ruby_smb 1.0.3 → 2.0.1

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.
Files changed (200) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.travis.yml +3 -2
  5. data/Gemfile +6 -2
  6. data/README.md +35 -47
  7. data/examples/enum_registry_key.rb +28 -0
  8. data/examples/enum_registry_values.rb +30 -0
  9. data/examples/negotiate.rb +51 -8
  10. data/examples/pipes.rb +2 -1
  11. data/examples/read_file_encryption.rb +56 -0
  12. data/examples/read_registry_key_value.rb +32 -0
  13. data/lib/ruby_smb.rb +4 -1
  14. data/lib/ruby_smb/client.rb +233 -22
  15. data/lib/ruby_smb/client/authentication.rb +70 -33
  16. data/lib/ruby_smb/client/echo.rb +20 -2
  17. data/lib/ruby_smb/client/encryption.rb +62 -0
  18. data/lib/ruby_smb/client/negotiation.rb +172 -24
  19. data/lib/ruby_smb/client/signing.rb +19 -0
  20. data/lib/ruby_smb/client/tree_connect.rb +24 -18
  21. data/lib/ruby_smb/client/utils.rb +8 -7
  22. data/lib/ruby_smb/client/winreg.rb +46 -0
  23. data/lib/ruby_smb/crypto.rb +30 -0
  24. data/lib/ruby_smb/dcerpc.rb +38 -0
  25. data/lib/ruby_smb/dcerpc/bind.rb +2 -2
  26. data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
  27. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  28. data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
  29. data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
  30. data/lib/ruby_smb/dcerpc/request.rb +28 -9
  31. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
  32. data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
  33. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
  34. data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
  35. data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
  36. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
  37. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
  38. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
  39. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
  40. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
  41. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
  42. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
  43. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
  44. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
  45. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
  46. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
  47. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
  48. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
  49. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
  50. data/lib/ruby_smb/dispatcher/socket.rb +4 -3
  51. data/lib/ruby_smb/error.rb +68 -2
  52. data/lib/ruby_smb/generic_packet.rb +33 -4
  53. data/lib/ruby_smb/smb1/commands.rb +1 -1
  54. data/lib/ruby_smb/smb1/file.rb +66 -15
  55. data/lib/ruby_smb/smb1/packet/close_request.rb +2 -5
  56. data/lib/ruby_smb/smb1/packet/close_response.rb +2 -1
  57. data/lib/ruby_smb/smb1/packet/echo_request.rb +2 -4
  58. data/lib/ruby_smb/smb1/packet/echo_response.rb +2 -1
  59. data/lib/ruby_smb/smb1/packet/empty_packet.rb +10 -1
  60. data/lib/ruby_smb/smb1/packet/logoff_request.rb +2 -4
  61. data/lib/ruby_smb/smb1/packet/logoff_response.rb +2 -1
  62. data/lib/ruby_smb/smb1/packet/negotiate_request.rb +2 -5
  63. data/lib/ruby_smb/smb1/packet/negotiate_response.rb +3 -7
  64. data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +4 -4
  65. data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +2 -4
  66. data/lib/ruby_smb/smb1/packet/nt_create_andx_response.rb +2 -1
  67. data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +2 -1
  68. data/lib/ruby_smb/smb1/packet/nt_trans/create_response.rb +2 -1
  69. data/lib/ruby_smb/smb1/packet/nt_trans/request.rb +2 -4
  70. data/lib/ruby_smb/smb1/packet/nt_trans/response.rb +2 -1
  71. data/lib/ruby_smb/smb1/packet/read_andx_request.rb +2 -5
  72. data/lib/ruby_smb/smb1/packet/read_andx_response.rb +2 -1
  73. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +2 -1
  74. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +3 -2
  75. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +2 -5
  76. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +3 -2
  77. data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_request.rb +0 -1
  78. data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_response.rb +3 -2
  79. data/lib/ruby_smb/smb1/packet/trans/request.rb +2 -5
  80. data/lib/ruby_smb/smb1/packet/trans/response.rb +2 -1
  81. data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_request.rb +1 -1
  82. data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_response.rb +1 -1
  83. data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +2 -1
  84. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +8 -2
  85. data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +2 -1
  86. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +8 -2
  87. data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +2 -1
  88. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +2 -1
  89. data/lib/ruby_smb/smb1/packet/trans2/request.rb +2 -4
  90. data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +2 -4
  91. data/lib/ruby_smb/smb1/packet/trans2/response.rb +2 -1
  92. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +2 -1
  93. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +2 -1
  94. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +2 -4
  95. data/lib/ruby_smb/smb1/packet/tree_connect_response.rb +13 -3
  96. data/lib/ruby_smb/smb1/packet/tree_disconnect_request.rb +2 -4
  97. data/lib/ruby_smb/smb1/packet/tree_disconnect_response.rb +2 -1
  98. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +3 -6
  99. data/lib/ruby_smb/smb1/packet/write_andx_response.rb +2 -1
  100. data/lib/ruby_smb/smb1/pipe.rb +87 -6
  101. data/lib/ruby_smb/smb1/tree.rb +50 -3
  102. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  103. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  104. data/lib/ruby_smb/smb2/file.rb +103 -25
  105. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  106. data/lib/ruby_smb/smb2/packet.rb +2 -0
  107. data/lib/ruby_smb/smb2/packet/close_request.rb +2 -4
  108. data/lib/ruby_smb/smb2/packet/close_response.rb +2 -1
  109. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  110. data/lib/ruby_smb/smb2/packet/create_request.rb +2 -4
  111. data/lib/ruby_smb/smb2/packet/create_response.rb +2 -1
  112. data/lib/ruby_smb/smb2/packet/echo_request.rb +2 -4
  113. data/lib/ruby_smb/smb2/packet/echo_response.rb +2 -1
  114. data/lib/ruby_smb/smb2/packet/error_packet.rb +15 -3
  115. data/lib/ruby_smb/smb2/packet/ioctl_request.rb +2 -5
  116. data/lib/ruby_smb/smb2/packet/ioctl_response.rb +2 -1
  117. data/lib/ruby_smb/smb2/packet/logoff_request.rb +2 -4
  118. data/lib/ruby_smb/smb2/packet/logoff_response.rb +2 -1
  119. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -17
  120. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +52 -5
  121. data/lib/ruby_smb/smb2/packet/query_directory_request.rb +2 -4
  122. data/lib/ruby_smb/smb2/packet/query_directory_response.rb +8 -2
  123. data/lib/ruby_smb/smb2/packet/read_request.rb +2 -4
  124. data/lib/ruby_smb/smb2/packet/read_response.rb +2 -1
  125. data/lib/ruby_smb/smb2/packet/session_setup_request.rb +2 -5
  126. data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -1
  127. data/lib/ruby_smb/smb2/packet/set_info_request.rb +2 -4
  128. data/lib/ruby_smb/smb2/packet/set_info_response.rb +2 -1
  129. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  130. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +93 -10
  131. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +10 -22
  132. data/lib/ruby_smb/smb2/packet/tree_disconnect_request.rb +2 -4
  133. data/lib/ruby_smb/smb2/packet/tree_disconnect_response.rb +2 -1
  134. data/lib/ruby_smb/smb2/packet/write_request.rb +2 -4
  135. data/lib/ruby_smb/smb2/packet/write_response.rb +2 -1
  136. data/lib/ruby_smb/smb2/pipe.rb +86 -12
  137. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  138. data/lib/ruby_smb/smb2/tree.rb +65 -21
  139. data/lib/ruby_smb/version.rb +1 -1
  140. data/ruby_smb.gemspec +5 -3
  141. data/spec/lib/ruby_smb/client_spec.rb +1612 -108
  142. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  143. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
  144. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
  145. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
  146. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
  147. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
  148. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
  149. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
  150. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
  151. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
  152. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
  153. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
  154. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
  155. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
  156. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
  157. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
  158. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
  159. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
  160. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
  161. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
  162. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
  163. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
  164. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
  165. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
  166. data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
  167. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +2 -2
  168. data/spec/lib/ruby_smb/error_spec.rb +59 -0
  169. data/spec/lib/ruby_smb/generic_packet_spec.rb +52 -4
  170. data/spec/lib/ruby_smb/smb1/file_spec.rb +191 -2
  171. data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +68 -0
  172. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  173. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  174. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  175. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  176. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +11 -2
  177. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +11 -2
  178. data/spec/lib/ruby_smb/smb1/packet/tree_connect_response_spec.rb +40 -0
  179. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +272 -149
  180. data/spec/lib/ruby_smb/smb1/tree_spec.rb +44 -7
  181. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  182. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  183. data/spec/lib/ruby_smb/smb2/file_spec.rb +323 -6
  184. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  185. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  186. data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +78 -0
  187. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  188. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  189. data/spec/lib/ruby_smb/smb2/packet/query_directory_response_spec.rb +8 -0
  190. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  191. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  192. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -22
  193. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +286 -149
  194. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  195. data/spec/lib/ruby_smb/smb2/tree_spec.rb +261 -2
  196. metadata +191 -83
  197. metadata.gz.sig +0 -0
  198. data/lib/ruby_smb/smb1/dcerpc.rb +0 -67
  199. data/lib/ruby_smb/smb2/dcerpc.rb +0 -70
  200. data/spec/lib/ruby_smb/smb1/packet/error_packet_spec.rb +0 -37
@@ -46,25 +46,34 @@ RSpec.describe RubySMB::SMB1::Tree do
46
46
  end
47
47
 
48
48
  describe '#disconnect!' do
49
- it 'calls #set_header_fields' do
49
+ let(:disco_response) { double('Response') }
50
+
51
+ before :example do
50
52
  allow(RubySMB::SMB1::Packet::TreeDisconnectRequest).to receive(:new).and_return(disco_req)
51
- allow(client).to receive(:send_recv).and_return(disco_resp.to_binary_s)
53
+ allow(RubySMB::SMB1::Packet::TreeDisconnectResponse).to receive(:read).and_return(disco_resp)
54
+ allow(client).to receive(:send_recv)
55
+ end
56
+
57
+ it 'calls #set_header_fields' do
52
58
  expect(tree).to receive(:set_header_fields).with(disco_req)
53
59
  tree.disconnect!
54
60
  end
55
61
 
56
62
  it 'sends a TreeDisconnectRequest with the Tree ID in the header' do
57
- allow(RubySMB::SMB1::Packet::TreeDisconnectRequest).to receive(:new).and_return(disco_req)
58
63
  modified_req = disco_req
59
64
  modified_req.smb_header.tid = tree.id
60
- expect(client).to receive(:send_recv).with(modified_req).and_return(disco_resp.to_binary_s)
65
+ expect(client).to receive(:send_recv).with(modified_req)
61
66
  tree.disconnect!
62
67
  end
63
68
 
64
69
  it 'returns the NTStatus code from the response' do
65
- allow(client).to receive(:send_recv).and_return(disco_resp.to_binary_s)
66
70
  expect(tree.disconnect!).to eq disco_resp.status_code
67
71
  end
72
+
73
+ it 'raises an InvalidPacket exception if the response is not valid' do
74
+ allow(disco_resp).to receive(:valid?).and_return(false)
75
+ expect { tree.disconnect! }.to raise_error(RubySMB::Error::InvalidPacket)
76
+ end
68
77
  end
69
78
 
70
79
  describe '#open_file' do
@@ -239,14 +248,14 @@ RSpec.describe RubySMB::SMB1::Tree do
239
248
  end
240
249
 
241
250
  context 'when the response is not a NtCreateAndxResponse packet' do
242
- it 'raise an InvalidPacket exception' do
251
+ it 'raises an InvalidPacket exception' do
243
252
  nt_create_andx_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_ECHO
244
253
  expect { tree.open_file(filename: filename) }.to raise_error(RubySMB::Error::InvalidPacket)
245
254
  end
246
255
  end
247
256
 
248
257
  context 'when the response status code is not STATUS_SUCCESS' do
249
- it 'raise an UnexpectedStatusCode exception' do
258
+ it 'raises an UnexpectedStatusCode exception' do
250
259
  nt_create_andx_response.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
251
260
  expect { tree.open_file(filename: filename) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
252
261
  end
@@ -342,6 +351,20 @@ RSpec.describe RubySMB::SMB1::Tree do
342
351
  expect(tree.list).to eq([file_info1, file_info2])
343
352
  end
344
353
 
354
+ context 'when the response is not a valid Trans2 FindFirst2Response' do
355
+ it 'raises an InvalidPacket exception' do
356
+ find_first2_res.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_ECHO
357
+ expect { tree.list }.to raise_error(RubySMB::Error::InvalidPacket)
358
+ end
359
+ end
360
+
361
+ context 'when the response status code is not STATUS_SUCCESS' do
362
+ it 'raises an UnexpectedStatusCode exception' do
363
+ find_first2_res.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
364
+ expect { tree.list }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
365
+ end
366
+ end
367
+
345
368
  context 'when more requests are needed to get all the information' do
346
369
  let(:find_next2_req) { RubySMB::SMB1::Packet::Trans2::FindNext2Request.new }
347
370
  let(:file_info2) do
@@ -414,6 +437,20 @@ RSpec.describe RubySMB::SMB1::Tree do
414
437
  it 'returns the expected FindFileFullDirectoryInfo structures' do
415
438
  expect(tree.list).to eq([file_info1, file_info2])
416
439
  end
440
+
441
+ context 'when the response is not a valid Trans2 FindNext2Response' do
442
+ it 'raises an InvalidPacket exception' do
443
+ find_next2_res.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_ECHO
444
+ expect { tree.list }.to raise_error(RubySMB::Error::InvalidPacket)
445
+ end
446
+ end
447
+
448
+ context 'when the response status code is not STATUS_SUCCESS' do
449
+ it 'raises an UnexpectedStatusCode exception' do
450
+ find_next2_res.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
451
+ expect { tree.list }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
452
+ end
453
+ end
417
454
  end
418
455
  end
419
456
 
@@ -5,6 +5,7 @@ RSpec.describe RubySMB::SMB2::BitField::SessionFlags do
5
5
 
6
6
  it { is_expected.to respond_to :guest }
7
7
  it { is_expected.to respond_to :null }
8
+ it { is_expected.to respond_to :encrypt_data }
8
9
 
9
10
  it 'is little endian' do
10
11
  expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
@@ -25,4 +26,12 @@ RSpec.describe RubySMB::SMB2::BitField::SessionFlags do
25
26
 
26
27
  it_behaves_like 'bit field with one flag set', :null, 'v', 0x00000002
27
28
  end
29
+
30
+ describe '#encrypt_data' do
31
+ it 'should be a 1-bit field per the SMB spec' do
32
+ expect(flags.encrypt_data).to be_a BinData::Bit1
33
+ end
34
+
35
+ it_behaves_like 'bit field with one flag set', :encrypt_data, 'v', 0x00000004
36
+ end
28
37
  end
@@ -1,6 +1,8 @@
1
1
  RSpec.describe RubySMB::SMB2::BitField::ShareFlags do
2
2
  subject(:flags) { described_class.new }
3
3
 
4
+ it { is_expected.to respond_to :vdo_caching }
5
+ it { is_expected.to respond_to :auto_caching }
4
6
  it { is_expected.to respond_to :dfs_root }
5
7
  it { is_expected.to respond_to :dfs }
6
8
  it { is_expected.to respond_to :encrypt }
@@ -11,11 +13,28 @@ RSpec.describe RubySMB::SMB2::BitField::ShareFlags do
11
13
  it { is_expected.to respond_to :namespace_caching }
12
14
  it { is_expected.to respond_to :shared_delete }
13
15
  it { is_expected.to respond_to :restrict_exclusive_opens }
16
+ it { is_expected.to respond_to :identity_remoting }
14
17
 
15
18
  it 'is little endian' do
16
19
  expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
17
20
  end
18
21
 
22
+ describe '#vdo_caching' do
23
+ it 'should be a 1-bit field per the SMB spec' do
24
+ expect(flags.vdo_caching).to be_a BinData::Bit1
25
+ end
26
+
27
+ it_behaves_like 'bit field with one flag set', :vdo_caching, 'V', 0x00000020
28
+ end
29
+
30
+ describe '#auto_caching' do
31
+ it 'should be a 1-bit field per the SMB spec' do
32
+ expect(flags.auto_caching).to be_a BinData::Bit1
33
+ end
34
+
35
+ it_behaves_like 'bit field with one flag set', :auto_caching, 'V', 0x00000010
36
+ end
37
+
19
38
  describe '#dfs' do
20
39
  it 'should be a 1-bit field per the SMB spec' do
21
40
  expect(flags.dfs).to be_a BinData::Bit1
@@ -96,6 +115,14 @@ RSpec.describe RubySMB::SMB2::BitField::ShareFlags do
96
115
  it_behaves_like 'bit field with one flag set', :encrypt, 'V', 0x00008000
97
116
  end
98
117
 
118
+ describe '#identity_remoting' do
119
+ it 'should be a 1-bit field per the SMB spec' do
120
+ expect(flags.identity_remoting).to be_a BinData::Bit1
121
+ end
122
+
123
+ it_behaves_like 'bit field with one flag set', :identity_remoting, 'V', 0x00040000
124
+ end
125
+
99
126
  describe '#set_manual_caching' do
100
127
  it 'turns off the caching bits' do
101
128
  flags.set_manual_caching
@@ -43,6 +43,7 @@ RSpec.describe RubySMB::SMB2::File do
43
43
  it { is_expected.to respond_to :size }
44
44
  it { is_expected.to respond_to :size_on_disk }
45
45
  it { is_expected.to respond_to :tree }
46
+ it { is_expected.to respond_to :tree_connect_encrypt_data }
46
47
 
47
48
  it 'pulls the attributes from the response packet' do
48
49
  expect(file.attributes).to eq create_response.file_attributes
@@ -52,10 +53,18 @@ RSpec.describe RubySMB::SMB2::File do
52
53
  expect(file.guid).to eq create_response.file_id
53
54
  end
54
55
 
55
- it 'pulls the timestamps from the response packet' do
56
+ it 'pulls the last access timestamps from the response packet' do
56
57
  expect(file.last_access).to eq create_response.last_access.to_datetime
57
58
  end
58
59
 
60
+ it 'pulls the last change timestamps from the response packet' do
61
+ expect(file.last_change).to eq create_response.last_change.to_datetime
62
+ end
63
+
64
+ it 'pulls the last write timestamps from the response packet' do
65
+ expect(file.last_write).to eq create_response.last_write.to_datetime
66
+ end
67
+
59
68
  it 'pulls the size from the response packet' do
60
69
  expect(file.size).to eq create_response.end_of_file
61
70
  end
@@ -64,6 +73,10 @@ RSpec.describe RubySMB::SMB2::File do
64
73
  expect(file.size_on_disk).to eq create_response.allocation_size
65
74
  end
66
75
 
76
+ it 'sets the tree_connect_encrypt_data flag to false by default' do
77
+ expect(file.tree_connect_encrypt_data).to be false
78
+ end
79
+
67
80
  describe '#set_header_fields' do
68
81
  let(:request) { RubySMB::SMB2::Packet::ReadRequest.new }
69
82
  it 'calls the set_header_field method from the Tree' do
@@ -99,19 +112,59 @@ RSpec.describe RubySMB::SMB2::File do
99
112
  describe '#read' do
100
113
  context 'for a small file' do
101
114
  let(:small_read) { file.read_packet(read_length: 108) }
102
- let(:small_response) { RubySMB::SMB2::Packet::ReadResponse.new(data_length: 9, buffer: 'fake data') }
115
+ let(:small_response) {
116
+ response = RubySMB::SMB2::Packet::ReadResponse.new(data_length: 9, buffer: 'fake data')
117
+ response.smb2_header.command = RubySMB::SMB2::Commands::READ
118
+ response
119
+ }
120
+
121
+ before :example do
122
+ allow(file).to receive(:read_packet)
123
+ allow(client).to receive(:send_recv)
124
+ allow(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).and_return(small_response)
125
+ end
103
126
 
104
127
  it 'uses a single packet to read the entire file' do
105
128
  expect(file).to receive(:read_packet).with(read_length: 108, offset: 0).and_return(small_read)
106
- expect(client).to receive(:send_recv).with(small_read).and_return 'fake data'
129
+ expect(client).to receive(:send_recv).with(small_read, encrypt: false).and_return 'fake data'
107
130
  expect(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).with('fake data').and_return(small_response)
108
131
  expect(file.read).to eq 'fake data'
109
132
  end
133
+
134
+ it 'calls Client #send_recv with encryption set if required' do
135
+ file.tree_connect_encrypt_data = true
136
+ expect(client).to receive(:send_recv).with(small_read, encrypt: true)
137
+ file.read
138
+ end
139
+
140
+ context 'when the response is not valid' do
141
+ it 'raise an InvalidPacket exception' do
142
+ small_response.smb2_header.command = RubySMB::SMB2::Commands::LOGOFF
143
+ expect { file.read }.to raise_error(RubySMB::Error::InvalidPacket)
144
+ end
145
+ end
146
+
147
+ context 'when the response status code is not STATUS_SUCCESS' do
148
+ it 'raise an UnexpectedStatusCode exception' do
149
+ small_response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
150
+ expect { file.read }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
151
+ end
152
+ end
110
153
  end
111
154
 
112
155
  context 'for a larger file' do
113
156
  let(:big_read) { file.read_packet(read_length: 108) }
114
- let(:big_response) { RubySMB::SMB2::Packet::ReadResponse.new(data_length: 9, buffer: 'fake data') }
157
+ let(:big_response) {
158
+ response = RubySMB::SMB2::Packet::ReadResponse.new(data_length: 9, buffer: 'fake data')
159
+ response.smb2_header.command = RubySMB::SMB2::Commands::READ
160
+ response
161
+ }
162
+
163
+ before :example do
164
+ allow(file).to receive(:read_packet)
165
+ allow(client).to receive(:send_recv)
166
+ allow(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).and_return(big_response)
167
+ end
115
168
 
116
169
  it 'uses a multiple packet to read the file in chunks' do
117
170
  expect(file).to receive(:read_packet).once.with(read_length: described_class::MAX_PACKET_SIZE, offset: 0).and_return(big_read)
@@ -120,6 +173,32 @@ RSpec.describe RubySMB::SMB2::File do
120
173
  expect(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).twice.with('fake data').and_return(big_response)
121
174
  file.read(bytes: (described_class::MAX_PACKET_SIZE * 2))
122
175
  end
176
+
177
+ it 'calls Client #send_recv with encryption set if required' do
178
+ read_request = double('Read Request')
179
+ allow(file).to receive(:read_packet).and_return(read_request)
180
+ file.tree_connect_encrypt_data = true
181
+ expect(client).to receive(:send_recv).twice.with(read_request, encrypt: true)
182
+ file.read(bytes: (described_class::MAX_PACKET_SIZE * 2))
183
+ end
184
+
185
+ context 'when the second response is not valid' do
186
+ 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
188
+ big_response.smb2_header.command = RubySMB::SMB2::Commands::LOGOFF
189
+ end
190
+ expect { file.read(bytes: (described_class::MAX_PACKET_SIZE * 2)) }.to raise_error(RubySMB::Error::InvalidPacket)
191
+ end
192
+ end
193
+
194
+ context 'when the second response status code is not STATUS_SUCCESS' do
195
+ 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
197
+ big_response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
198
+ end
199
+ expect { file.read(bytes: (described_class::MAX_PACKET_SIZE * 2)) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
200
+ end
201
+ end
123
202
  end
124
203
  end
125
204
 
@@ -152,6 +231,14 @@ RSpec.describe RubySMB::SMB2::File do
152
231
  expect(client).to receive(:send_recv).once.and_return(write_response.to_binary_s)
153
232
  file.write(data: 'test')
154
233
  end
234
+
235
+ it 'calls Client #send_recv with encryption set if required' do
236
+ write_request = double('Write Request')
237
+ allow(file).to receive(:write_packet).and_return(write_request)
238
+ file.tree_connect_encrypt_data = true
239
+ expect(client).to receive(:send_recv).once.with(write_request, encrypt: true).and_return(write_response.to_binary_s)
240
+ file.write(data: 'test')
241
+ end
155
242
  end
156
243
 
157
244
  context 'for a large write' do
@@ -159,6 +246,21 @@ RSpec.describe RubySMB::SMB2::File do
159
246
  expect(client).to receive(:send_recv).twice.and_return(write_response.to_binary_s)
160
247
  file.write(data: SecureRandom.random_bytes(described_class::MAX_PACKET_SIZE + 1))
161
248
  end
249
+
250
+ it 'calls Client #send_recv with encryption set if required' do
251
+ write_request = double('Write Request')
252
+ allow(file).to receive(:write_packet).and_return(write_request)
253
+ file.tree_connect_encrypt_data = true
254
+ expect(client).to receive(:send_recv).twice.with(write_request, encrypt: true).and_return(write_response.to_binary_s)
255
+ file.write(data: SecureRandom.random_bytes(described_class::MAX_PACKET_SIZE + 1))
256
+ end
257
+ end
258
+
259
+ it 'raises an InvalidPacket exception if the response is not valid' do
260
+ allow(client).to receive(:send_recv)
261
+ allow(RubySMB::SMB2::Packet::WriteResponse).to receive(:read).and_return(write_response)
262
+ allow(write_response).to receive(:valid?).and_return(false)
263
+ expect { file.write(data: 'test') }.to raise_error(RubySMB::Error::InvalidPacket)
162
264
  end
163
265
  end
164
266
 
@@ -189,10 +291,26 @@ RSpec.describe RubySMB::SMB2::File do
189
291
 
190
292
  it 'uses a single packet to delete the entire file' do
191
293
  expect(file).to receive(:delete_packet).and_return(small_delete)
192
- expect(client).to receive(:send_recv).with(small_delete).and_return 'raw_response'
294
+ expect(client).to receive(:send_recv).with(small_delete, encrypt: false).and_return 'raw_response'
193
295
  expect(RubySMB::SMB2::Packet::SetInfoResponse).to receive(:read).with('raw_response').and_return(small_response)
194
296
  expect(file.delete).to eq WindowsError::NTStatus::STATUS_SUCCESS
195
297
  end
298
+
299
+ it 'raises an InvalidPacket exception if the response is not valid' do
300
+ allow(file).to receive(:delete_packet)
301
+ allow(client).to receive(:send_recv)
302
+ allow(RubySMB::SMB2::Packet::SetInfoResponse).to receive(:read).and_return(small_response)
303
+ allow(small_response).to receive(:valid?).and_return(false)
304
+ expect { file.delete }.to raise_error(RubySMB::Error::InvalidPacket)
305
+ end
306
+
307
+ it 'calls Client #send_recv with encryption set if required' do
308
+ allow(file).to receive(:delete_packet)
309
+ allow(RubySMB::SMB2::Packet::SetInfoResponse).to receive(:read).and_return(small_response)
310
+ file.tree_connect_encrypt_data = true
311
+ expect(client).to receive(:send_recv).with(small_delete, encrypt: true)
312
+ file.delete
313
+ end
196
314
  end
197
315
  end
198
316
 
@@ -224,10 +342,209 @@ RSpec.describe RubySMB::SMB2::File do
224
342
 
225
343
  it 'uses a single packet to rename the entire file' do
226
344
  expect(file).to receive(:rename_packet).and_return(small_rename)
227
- expect(client).to receive(:send_recv).with(small_rename).and_return 'raw_response'
345
+ expect(client).to receive(:send_recv).with(small_rename, encrypt: false).and_return 'raw_response'
228
346
  expect(RubySMB::SMB2::Packet::SetInfoResponse).to receive(:read).with('raw_response').and_return(small_response)
229
347
  expect(file.rename('new_file.txt')).to eq WindowsError::NTStatus::STATUS_SUCCESS
230
348
  end
349
+
350
+ it 'calls Client #send_recv with encryption set if required' do
351
+ allow(RubySMB::SMB2::Packet::SetInfoResponse).to receive(:read).and_return(small_response)
352
+ file.tree_connect_encrypt_data = true
353
+ expect(client).to receive(:send_recv).with(small_rename, encrypt: true)
354
+ file.rename('new_file.txt')
355
+ end
356
+
357
+ it 'raises an InvalidPacket exception if the response is not valid' do
358
+ allow(client).to receive(:send_recv)
359
+ allow(RubySMB::SMB2::Packet::SetInfoResponse).to receive(:read).and_return(small_response)
360
+ allow(small_response).to receive(:valid?).and_return(false)
361
+ expect { file.rename('new_file.txt') }.to raise_error(RubySMB::Error::InvalidPacket)
362
+ end
363
+ end
364
+ end
365
+
366
+ describe '#close' do
367
+ let(:request) { double('CloseRequest') }
368
+ let(:response) { double('CloseResponse') }
369
+ let(:raw_response) { double('Raw response') }
370
+
371
+ before :example do
372
+ allow(RubySMB::SMB2::Packet::CloseRequest).to receive(:new).and_return(request)
373
+ allow(file).to receive(:set_header_fields).and_return(request)
374
+ allow(client).to receive(:send_recv).and_return(raw_response)
375
+ allow(RubySMB::SMB2::Packet::CloseResponse).to receive(:read).and_return(response)
376
+ allow(response).to receive(:valid?).and_return(true)
377
+ allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_SUCCESS)
378
+ end
379
+
380
+ it 'creates a new SMB2 CloseRequest packet' do
381
+ expect(RubySMB::SMB2::Packet::CloseRequest).to receive(:new)
382
+ file.close
383
+ end
384
+
385
+ it 'calls Tree #set_header_fields to set SetFileInformationRequest headers' do
386
+ expect(file).to receive(:set_header_fields).with(request)
387
+ file.close
388
+ end
389
+
390
+ it 'calls Client #send_recv with the expected request' do
391
+ expect(client).to receive(:send_recv).with(request, encrypt: false)
392
+ file.close
393
+ end
394
+
395
+ it 'calls Client #send_recv with encryption set if required' do
396
+ file.tree_connect_encrypt_data = true
397
+ expect(client).to receive(:send_recv).with(request, encrypt: true)
398
+ file.close
399
+ end
400
+
401
+ it 'parses the response as a SMB2 CloseResponse packet' do
402
+ expect(RubySMB::SMB2::Packet::CloseResponse).to receive(:read).with(raw_response)
403
+ file.close
404
+ end
405
+
406
+ it 'raises an InvalidPacket exception if the response is not valid' do
407
+ 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)
411
+ expect { file.close }.to raise_error(RubySMB::Error::InvalidPacket)
412
+ end
413
+
414
+ it 'raises an UnexpectedStatusCode exception if the response status code is not STATUS_SUCCESS' do
415
+ allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND)
416
+ expect { file.close }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
417
+ end
418
+
419
+ it 'returns the response status code' do
420
+ expect(file.close).to eq WindowsError::NTStatus::STATUS_SUCCESS
421
+ end
422
+ end
423
+
424
+ describe '#send_recv_read' do
425
+ let(:read_data) { 'read data' }
426
+ let(:raw_response) { double('fake raw response data') }
427
+ let(:read_response) {
428
+ res = RubySMB::SMB2::Packet::ReadResponse.new
429
+ res.data_length = read_data.size
430
+ res.buffer = read_data
431
+ res
432
+ }
433
+
434
+ before :example do
435
+ allow(client).to receive(:send_recv).and_return(raw_response)
436
+ allow(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).with(raw_response).and_return(read_response)
437
+ end
438
+
439
+ context 'when the number of bytes to read is not provided' do
440
+ it 'reads 0 bytes by default' do
441
+ expect(file).to receive(:read_packet).with(read_length: 0, offset: 0).once.and_call_original
442
+ file.send_recv_read
443
+ end
444
+ end
445
+
446
+ it 'only reads the number of bytes provided as argument' do
447
+ bytes = 5
448
+ expect(file).to receive(:read_packet).with(read_length: bytes, offset: 0).once.and_call_original
449
+ file.send_recv_read(read_length: bytes)
450
+ end
451
+
452
+ it 'reads from the offset provided as argument' do
453
+ offset = 3
454
+ expect(file).to receive(:read_packet).with(read_length: 0, offset: offset).once.and_call_original
455
+ file.send_recv_read(offset: offset)
456
+ end
457
+
458
+ it 'calls Client #send_recv with the expected request' do
459
+ request = double('Request')
460
+ allow(file).to receive(:read_packet).and_return(request)
461
+ expect(client).to receive(:send_recv).with(request, encrypt: false)
462
+ file.send_recv_read
463
+ end
464
+
465
+ it 'calls Client #send_recv with encryption set if required' do
466
+ request = double('Request')
467
+ allow(file).to receive(:read_packet).and_return(request)
468
+ file.tree_connect_encrypt_data = true
469
+ expect(client).to receive(:send_recv).with(request, encrypt: true)
470
+ file.send_recv_read
471
+ end
472
+
473
+ it 'parses the response as a SMB2 ReadResponse packet' do
474
+ expect(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).with(raw_response)
475
+ file.send_recv_read
476
+ end
477
+
478
+ it 'raises an InvalidPacket exception if the response is not valid' do
479
+ allow(read_response).to receive(:valid?).and_return(false)
480
+ expect { file.send_recv_read }.to raise_error(RubySMB::Error::InvalidPacket)
481
+ end
482
+
483
+ it 'raises an UnexpectedStatusCode exception if the response status code is not STATUS_SUCCESS' do
484
+ allow(read_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND)
485
+ expect { file.send_recv_read }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
486
+ end
487
+
488
+ it 'returns the expected string' do
489
+ expect(file.send_recv_read).to eq(read_data)
490
+ end
491
+ end
492
+
493
+ describe '#send_recv_write' do
494
+ let(:write_data) { 'write data' }
495
+ let(:request) { double('Request') }
496
+ let(:raw_response) { double('fake raw response data') }
497
+ let(:write_response) {
498
+ res = RubySMB::SMB2::Packet::WriteResponse.new
499
+ res.write_count = write_data.size
500
+ res
501
+ }
502
+
503
+ before :example do
504
+ allow(file).to receive(:write_packet).and_return(request)
505
+ allow(client).to receive(:send_recv).and_return(raw_response, encrypt: false)
506
+ allow(RubySMB::SMB2::Packet::WriteResponse).to receive(:read).with(raw_response).and_return(write_response)
507
+ end
508
+
509
+ it 'reads 0 bytes from offset 0 by default' do
510
+ expect(file).to receive(:write_packet).with(data: '', offset: 0).once.and_call_original
511
+ file.send_recv_write
512
+ end
513
+
514
+ it 'writes the data provided as argument' do
515
+ expect(file).to receive(:write_packet).with(data: write_data, offset: 0).once.and_call_original
516
+ file.send_recv_write(data: write_data)
517
+ end
518
+
519
+ it 'reads from the offset provided as argument' do
520
+ offset = 3
521
+ expect(file).to receive(:write_packet).with(data: '', offset: offset).once.and_call_original
522
+ file.send_recv_write(offset: offset)
523
+ end
524
+
525
+ it 'calls Client #send_recv with the expected request' do
526
+ expect(client).to receive(:send_recv).with(request, encrypt: false)
527
+ file.send_recv_write
528
+ end
529
+
530
+ it 'calls Client #send_recv with encryption set if required' do
531
+ file.tree_connect_encrypt_data = true
532
+ expect(client).to receive(:send_recv).with(request, encrypt: true)
533
+ file.send_recv_write
534
+ end
535
+
536
+ it 'parses the response as a SMB1 WriteResponse packet' do
537
+ expect(RubySMB::SMB2::Packet::WriteResponse).to receive(:read).with(raw_response)
538
+ file.send_recv_write
539
+ end
540
+
541
+ it 'raises an InvalidPacket exception if the response is not valid' do
542
+ allow(write_response).to receive(:valid?).and_return(false)
543
+ expect { file.send_recv_write }.to raise_error(RubySMB::Error::InvalidPacket)
544
+ end
545
+
546
+ it 'returns the expected response #write_count value' do
547
+ expect(file.send_recv_write).to eq(write_data.size)
231
548
  end
232
549
  end
233
550
  end