ruby_smb 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -4
  4. data/.travis.yml +3 -5
  5. data/Gemfile +6 -2
  6. data/examples/negotiate.rb +51 -8
  7. data/examples/read_file_encryption.rb +56 -0
  8. data/lib/ruby_smb.rb +4 -0
  9. data/lib/ruby_smb/client.rb +172 -16
  10. data/lib/ruby_smb/client/authentication.rb +27 -8
  11. data/lib/ruby_smb/client/encryption.rb +62 -0
  12. data/lib/ruby_smb/client/negotiation.rb +133 -12
  13. data/lib/ruby_smb/client/signing.rb +19 -0
  14. data/lib/ruby_smb/client/tree_connect.rb +4 -4
  15. data/lib/ruby_smb/client/utils.rb +8 -7
  16. data/lib/ruby_smb/crypto.rb +30 -0
  17. data/lib/ruby_smb/dispatcher/socket.rb +2 -2
  18. data/lib/ruby_smb/error.rb +28 -1
  19. data/lib/ruby_smb/smb1/commands.rb +1 -1
  20. data/lib/ruby_smb/smb1/file.rb +4 -4
  21. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
  22. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
  23. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  24. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
  25. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
  26. data/lib/ruby_smb/smb1/pipe.rb +2 -2
  27. data/lib/ruby_smb/smb1/tree.rb +3 -3
  28. data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
  29. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
  30. data/lib/ruby_smb/smb2/file.rb +25 -43
  31. data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
  32. data/lib/ruby_smb/smb2/packet.rb +2 -0
  33. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
  34. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
  35. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +49 -3
  36. data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
  37. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
  38. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
  39. data/lib/ruby_smb/smb2/pipe.rb +3 -16
  40. data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
  41. data/lib/ruby_smb/smb2/tree.rb +23 -17
  42. data/lib/ruby_smb/version.rb +1 -1
  43. data/ruby_smb.gemspec +3 -1
  44. data/spec/lib/ruby_smb/client_spec.rb +1256 -57
  45. data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
  46. data/spec/lib/ruby_smb/error_spec.rb +59 -0
  47. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
  48. data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
  49. data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
  50. data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
  51. data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
  52. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
  53. data/spec/lib/ruby_smb/smb2/file_spec.rb +86 -62
  54. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
  55. data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
  56. data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
  57. data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
  58. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
  59. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
  60. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
  61. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +0 -40
  62. data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
  63. data/spec/lib/ruby_smb/smb2/tree_spec.rb +53 -8
  64. metadata +124 -75
  65. metadata.gz.sig +0 -0
@@ -61,12 +61,28 @@ RSpec.describe RubySMB::SMB2::Packet::NegotiateResponse do
61
61
  end
62
62
 
63
63
  describe '#negotiate_context_count' do
64
+ it 'only exists if the 0x0311 dialect is included' do
65
+ packet.dialect_revision = 0x0311
66
+ expect(packet.negotiate_context_count?).to be true
67
+ end
68
+
69
+ it 'does not exist if the 0x0311 dialect is not included' do
70
+ packet.dialect_revision = 0x0300
71
+ expect(packet.negotiate_context_count?).to be false
72
+ end
73
+
64
74
  it 'is a 16-bit unsigned integer' do
65
75
  expect(packet.negotiate_context_count).to be_a BinData::Uint16le
66
76
  end
67
77
 
68
- it 'has a default value of 0' do
69
- expect(packet.negotiate_context_count).to eq 0
78
+ it 'is set to the #negotiate_context_list array size' do
79
+ packet.dialect_revision = 0x0311
80
+ nc = RubySMB::SMB2::NegotiateContext.new(
81
+ context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
82
+ )
83
+ packet.negotiate_context_list << nc
84
+ packet.negotiate_context_list << nc
85
+ expect(packet.negotiate_context_count).to eq(2)
70
86
  end
71
87
  end
72
88
 
@@ -134,6 +150,16 @@ RSpec.describe RubySMB::SMB2::Packet::NegotiateResponse do
134
150
  end
135
151
 
136
152
  describe '#negotiate_context_offset' do
153
+ it 'only exists if the 0x0311 dialect is included' do
154
+ packet.dialect_revision = 0x0311
155
+ expect(packet.negotiate_context_offset?).to be true
156
+ end
157
+
158
+ it 'does not exist if the 0x0311 dialect is not included' do
159
+ packet.dialect_revision = 0x0300
160
+ expect(packet.negotiate_context_offset?).to be false
161
+ end
162
+
137
163
  it 'is a 32-bit unsigned integer' do
138
164
  expect(packet.negotiate_context_offset).to be_a BinData::Uint32le
139
165
  end
@@ -144,4 +170,96 @@ RSpec.describe RubySMB::SMB2::Packet::NegotiateResponse do
144
170
  expect(packet.security_buffer).to be_a BinData::String
145
171
  end
146
172
  end
173
+
174
+ describe '#pad' do
175
+ it 'only exists if the 0x0311 dialect is included' do
176
+ packet.dialect_revision = 0x0311
177
+ expect(packet.pad?).to be true
178
+ end
179
+
180
+ it 'does not exist if the 0x0311 dialect is not included' do
181
+ packet.dialect_revision = 0x0300
182
+ expect(packet.pad?).to be false
183
+ end
184
+
185
+ it 'should be a binary string' do
186
+ expect(packet.pad).to be_a BinData::String
187
+ end
188
+
189
+ it 'should keep #negotiate_context_list 8-byte aligned' do
190
+ packet.dialect_revision = 0x0311
191
+ expect(packet.negotiate_context_list.abs_offset % 8).to eq 0
192
+ end
193
+ end
194
+
195
+ describe '#negotiate_context_list' do
196
+ it 'only exists if the 0x0311 dialect is included' do
197
+ packet.dialect_revision = 0x0311
198
+ expect(packet.negotiate_context_list?).to be true
199
+ end
200
+
201
+ it 'does not exist if the 0x0311 dialect is not included' do
202
+ packet.dialect_revision = 0x0300
203
+ expect(packet.negotiate_context_list?).to be false
204
+ end
205
+
206
+ it 'is an array field as per the SMB spec' do
207
+ expect(packet.negotiate_context_list).to be_a BinData::Array
208
+ end
209
+ end
210
+
211
+ describe '#find_negotiate_context' do
212
+ before :example do
213
+ packet.add_negotiate_context(
214
+ RubySMB::SMB2::NegotiateContext.new(context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES)
215
+ )
216
+ packet.add_negotiate_context(
217
+ RubySMB::SMB2::NegotiateContext.new(context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES)
218
+ )
219
+ end
220
+
221
+ it 'returns the expected Negotiate Context structure' do
222
+ expect(packet.find_negotiate_context(RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES)).to eq(packet.negotiate_context_list[1])
223
+ end
224
+
225
+ it 'returns nil if the Negotiate Context structure is not found' do
226
+ expect(packet.find_negotiate_context(10)).to be nil
227
+ end
228
+ end
229
+
230
+ describe '#add_negotiate_context' do
231
+ it 'raises an ArgumentError exception if it is not a NegotiateContext structure' do
232
+ expect { packet.add_negotiate_context('nc') }.to raise_error(ArgumentError)
233
+ end
234
+
235
+ it 'updates the NegotiateContext#pad length to make sure the structure is 8-byte aligned' do
236
+ packet.dialect_revision = 0x0311
237
+ [
238
+ RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES,
239
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES,
240
+ RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES,
241
+ RubySMB::SMB2::NegotiateContext::SMB2_NETNAME_NEGOTIATE_CONTEXT_ID
242
+ ].each do |context_type|
243
+ nc = RubySMB::SMB2::NegotiateContext.new(context_type: context_type)
244
+ packet.add_negotiate_context(nc)
245
+ expect(packet.negotiate_context_list.last.context_type.abs_offset % 8).to eq 0
246
+ end
247
+ end
248
+ end
249
+
250
+ it 'reads binary data as expected' do
251
+ data = described_class.new
252
+ data.dialect_revision = 0x0311
253
+ data.security_buffer = 'security buf test'
254
+ [
255
+ RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES,
256
+ RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES,
257
+ RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES,
258
+ RubySMB::SMB2::NegotiateContext::SMB2_NETNAME_NEGOTIATE_CONTEXT_ID
259
+ ].each do |context_type|
260
+ nc = RubySMB::SMB2::NegotiateContext.new(context_type: context_type)
261
+ data.add_negotiate_context(nc)
262
+ expect(described_class.read(data.to_binary_s)).to eq(data)
263
+ end
264
+ end
147
265
  end
@@ -0,0 +1,220 @@
1
+ RSpec.describe RubySMB::SMB2::Packet::TransformHeader do
2
+ subject(:packet) { described_class.new }
3
+
4
+ it { is_expected.to respond_to :protocol }
5
+ it { is_expected.to respond_to :signature }
6
+ it { is_expected.to respond_to :nonce }
7
+ it { is_expected.to respond_to :original_message_size }
8
+ it { is_expected.to respond_to :flags }
9
+ it { is_expected.to respond_to :session_id }
10
+ it { is_expected.to respond_to :encrypted_data }
11
+
12
+ it 'is little endian' do
13
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
14
+ end
15
+
16
+ describe '#protocol' do
17
+ it 'is a 32-bit field' do
18
+ expect(packet.protocol).to be_a BinData::Bit32
19
+ end
20
+
21
+ it 'has an initial value of 0xFD534D42' do
22
+ expect(packet.protocol).to eq(0xFD534D42)
23
+ end
24
+ end
25
+
26
+ describe '#signature' do
27
+ it 'is a String' do
28
+ expect(packet.signature).to be_a BinData::String
29
+ end
30
+ end
31
+
32
+ describe '#nonce' do
33
+ it 'is a String' do
34
+ expect(packet.nonce).to be_a BinData::String
35
+ end
36
+ end
37
+
38
+ describe '#original_message_size ' do
39
+ it 'is a 32-bit unsigned integer' do
40
+ expect(packet.original_message_size).to be_a BinData::Uint32le
41
+ end
42
+ end
43
+
44
+ describe '#flags' do
45
+ it 'is a 16-bit unsigned integer' do
46
+ expect(packet.flags).to be_a BinData::Uint16le
47
+ end
48
+ end
49
+
50
+ describe '#session_id' do
51
+ it 'is a 64-bit unsigned integer' do
52
+ expect(packet.session_id).to be_a BinData::Uint64le
53
+ end
54
+ end
55
+
56
+ describe '#encrypted_data' do
57
+ it 'is an Array' do
58
+ expect(packet.encrypted_data).to be_a BinData::Array
59
+ end
60
+ end
61
+
62
+ describe '#decrypt' do
63
+ let(:key) { "\x56\x89\xd1\xbb\xf7\x45\xc0\xb6\x68\x81\x07\xe4\x7d\x35\xaf\xd3".b }
64
+ let(:data) { 'data'.b }
65
+ before :example do
66
+ packet.original_message_size = data.length
67
+ end
68
+
69
+ it 'raises the expected exception if the given algorithm is invalid' do
70
+ expect { packet.decrypt(key, algorithm: 'RC4') }.to raise_error(
71
+ RubySMB::Error::EncryptionError,
72
+ 'Error while decrypting with \'RC4\' (ArgumentError: Invalid algorithm, must be either AES-128-CCM or AES-128-GCM)'
73
+ )
74
+ end
75
+
76
+ context 'with AES-128-GCM algorithm (default)' do
77
+ before :example do
78
+ begin
79
+ OpenSSL::Cipher.new('AES-128-GCM')
80
+ rescue
81
+ skip(
82
+ "This test cannot be run since the version of OpenSSL the ruby "\
83
+ "OpenSSL extension was built with (#{OpenSSL::OPENSSL_VERSION}) "\
84
+ "does not support AES-128-GCM cipher")
85
+ end
86
+ packet.encrypted_data = "\x06\x45\x16\x36".bytes
87
+ packet.signature = "\x63\xb2\xf9\xe0\xb7\x43\xdb\xaf\x26\x8e\xd7\x42\xd3\xb2\xde\x0d"
88
+ packet.nonce = "\xe1\xb0\xa7\x20\xd9\xd9\x69\x3c\x79\xd0\x9c\x53\x00\x00\x00\x00"
89
+ end
90
+
91
+ it 'generates a cipher using OpenSSL::Cipher' do
92
+ expect(OpenSSL::Cipher).to receive(:new).with('AES-128-GCM').and_call_original
93
+ packet.decrypt(key)
94
+ end
95
+
96
+ it 'returns the expected decrypted string' do
97
+ expect(packet.decrypt(key)).to eq(data)
98
+ end
99
+
100
+ it 'raises the expected exception if an error occurs' do
101
+ allow(OpenSSL::Cipher).to receive(:new).and_raise(
102
+ RuntimeError.new('unsupported cipher algorithm (AES-128-GCM)'))
103
+ expect { packet.decrypt(key) }.to raise_error(
104
+ RubySMB::Error::EncryptionError,
105
+ 'Error while decrypting with \'AES-128-GCM\' (RuntimeError: unsupported cipher algorithm (AES-128-GCM))'
106
+ )
107
+ end
108
+ end
109
+
110
+ context 'with AES-128-CCM algorithm' do
111
+ before :example do
112
+ packet.encrypted_data = "\xf0\x05\x61\x91".bytes
113
+ packet.signature = "\xdd\x51\x9a\xc5\x6d\x38\x68\xdc\x36\x89\xb8\x99\xd8\x4a\xb8\x4a".b
114
+ packet.nonce = "\x8a\x6e\x2a\x87\x11\x61\x85\xd2\x15\x69\xf7\x00\x00\x00\x00\x00".b
115
+ end
116
+
117
+ it 'generates a cipher using OpenSSL::CCM' do
118
+ expect(OpenSSL::CCM).to receive(:new).with('AES', key, 16).and_call_original
119
+ packet.decrypt(key, algorithm: 'AES-128-CCM')
120
+ end
121
+
122
+ it 'returns the expected decrypted string' do
123
+ expect(packet.decrypt(key, algorithm: 'AES-128-CCM')).to eq(data)
124
+ end
125
+
126
+ it 'raises the expected exception if an error occurs' do
127
+ allow(OpenSSL::CCM).to receive(:new).and_raise(
128
+ OpenSSL::CCMError.new('unsupported cipher algorithm (AES-128-CCM)'))
129
+ expect { packet.decrypt(key, algorithm: 'AES-128-CCM') }.to raise_error(
130
+ RubySMB::Error::EncryptionError,
131
+ 'Error while decrypting with \'AES-128-CCM\' (OpenSSL::CCMError: unsupported cipher algorithm (AES-128-CCM))'
132
+ )
133
+ end
134
+ end
135
+ end
136
+
137
+ describe '#encrypt' do
138
+ let(:key) { "\x56\x89\xd1\xbb\xf7\x45\xc0\xb6\x68\x81\x07\xe4\x7d\x35\xaf\xd3".b }
139
+ let(:struct) { RubySMB::SMB2::Packet::TreeConnectRequest.new }
140
+
141
+ it 'raises the expected exception if the given algorithm is invalid' do
142
+ expect { packet.encrypt(struct, key, algorithm: 'RC4') }.to raise_error(
143
+ RubySMB::Error::EncryptionError,
144
+ 'Error while encrypting with \'RC4\' (ArgumentError: Invalid algorithm, must be either AES-128-CCM or AES-128-GCM)'
145
+ )
146
+ end
147
+
148
+ context 'with AES-128-GCM algorithm (default)' do
149
+ before :example do
150
+ begin
151
+ OpenSSL::Cipher.new('AES-128-GCM')
152
+ rescue
153
+ skip(
154
+ "This test cannot be run since the version of OpenSSL the ruby "\
155
+ "OpenSSL extension was built with (#{OpenSSL::OPENSSL_VERSION}) "\
156
+ "does not support AES-128-GCM cipher")
157
+ end
158
+ end
159
+
160
+ it 'generates a cipher using OpenSSL::Cipher' do
161
+ expect(OpenSSL::Cipher).to receive(:new).with('AES-128-GCM').and_call_original
162
+ packet.encrypt(struct, key)
163
+ end
164
+
165
+ it 'encrypts a BinData structure' do
166
+ packet.encrypt(struct, key)
167
+ expect(packet.decrypt(key)).to eq(struct.to_binary_s)
168
+ end
169
+
170
+ it 'encrypts a string' do
171
+ packet.encrypt('data', key)
172
+ expect(packet.decrypt(key)).to eq('data')
173
+ end
174
+
175
+ it 'raises the expected exception if an error occurs' do
176
+ allow(OpenSSL::Cipher).to receive(:new).and_raise(
177
+ RuntimeError.new('unsupported cipher algorithm (AES-128-GCM)'))
178
+ expect { packet.encrypt('data', key) }.to raise_error(
179
+ RubySMB::Error::EncryptionError,
180
+ 'Error while encrypting with \'AES-128-GCM\' (RuntimeError: unsupported cipher algorithm (AES-128-GCM))'
181
+ )
182
+ end
183
+ end
184
+
185
+ context 'with AES-128-CCM algorithm' do
186
+ it 'generates a cipher using OpenSSL::CCM' do
187
+ expect(OpenSSL::CCM).to receive(:new).with('AES', key, 16).and_call_original
188
+ packet.encrypt(struct, key, algorithm: 'AES-128-CCM')
189
+ end
190
+
191
+ it 'encrypts a BinData structure' do
192
+ packet.encrypt(struct, key, algorithm: 'AES-128-CCM')
193
+ expect(packet.decrypt(key, algorithm: 'AES-128-CCM')).to eq(struct.to_binary_s)
194
+ end
195
+
196
+ it 'encrypts a string' do
197
+ packet.encrypt('data', key, algorithm: 'AES-128-CCM')
198
+ expect(packet.decrypt(key, algorithm: 'AES-128-CCM')).to eq('data')
199
+ end
200
+
201
+ it 'raises the expected exception if an error occurs' do
202
+ allow(OpenSSL::CCM).to receive(:new).and_raise(
203
+ OpenSSL::CCMError.new('unsupported cipher algorithm (AES-128-CCM)'))
204
+ expect { packet.encrypt('data', key, algorithm: 'AES-128-CCM') }.to raise_error(
205
+ RubySMB::Error::EncryptionError,
206
+ 'Error while encrypting with \'AES-128-CCM\' (OpenSSL::CCMError: unsupported cipher algorithm (AES-128-CCM))'
207
+ )
208
+ end
209
+ end
210
+ end
211
+
212
+ it 'reads binary data as expected' do
213
+ data = described_class.new
214
+ key = "\x56\x89\xd1\xbb\xf7\x45\xc0\xb6\x68\x81\x07\xe4\x7d\x35\xaf\xd3".b
215
+ struct = RubySMB::SMB2::Packet::TreeConnectRequest.new
216
+ data.encrypt(struct, key, algorithm: 'AES-128-CCM')
217
+ expect(described_class.read(data.to_binary_s)).to eq(data)
218
+ end
219
+ end
220
+
@@ -9,6 +9,7 @@ RSpec.describe RubySMB::SMB2::Packet::TreeConnectRequest do
9
9
  it { is_expected.to respond_to :path_offset }
10
10
  it { is_expected.to respond_to :path_length }
11
11
  it { is_expected.to respond_to :path }
12
+ it { is_expected.to respond_to :tree_connect_request_extension }
12
13
 
13
14
  it 'is little endian' do
14
15
  expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
@@ -30,18 +31,347 @@ RSpec.describe RubySMB::SMB2::Packet::TreeConnectRequest do
30
31
  end
31
32
  end
32
33
 
33
- describe '#encode_path' do
34
- let(:path) { '\\192.168.1.1\\example' }
35
- let(:encoded_path) { path.encode('utf-16le').force_encoding('binary') }
34
+ describe '#structure_size' do
35
+ it 'should be a 16-bit unsigned integer' do
36
+ expect(packet.structure_size).to be_a BinData::Uint16le
37
+ end
38
+
39
+ it 'should have a default value of 9 as per the SMB2 spec' do
40
+ expect(packet.structure_size).to eq 9
41
+ end
42
+ end
43
+
44
+ describe '#flags' do
45
+ it 'should be a 16-bit unsigned integer' do
46
+ expect(packet.flags).to be_a BinData::Uint16le
47
+ end
48
+ end
49
+
50
+ describe '#path_offset' do
51
+ it 'should be a 16-bit unsigned integer' do
52
+ expect(packet.path_offset).to be_a BinData::Uint16le
53
+ end
54
+
55
+ context 'when flags is set to SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT' do
56
+ it 'should be set to the offset, in bytes, of the full share path name from the beginning of the packet header' do
57
+ packet.flags = described_class::SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT
58
+ expect(packet.path_offset).to eq(88)
59
+ end
60
+ end
61
+
62
+ context 'when flags is not set to SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT' do
63
+ it 'should be set to the offset, in bytes, of the full share path name from the beginning of the packet header' do
64
+ expect(packet.path_offset).to eq(72)
65
+ end
66
+ end
67
+ end
68
+
69
+ describe '#path_length' do
70
+ let(:path) { '\\\\server\\path' }
71
+
72
+ it 'should be a 16-bit unsigned integer' do
73
+ expect(packet.path_length).to be_a BinData::Uint16le
74
+ end
75
+
76
+ context 'when flags is set to SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT' do
77
+ it 'should be the length of the full share path name (unicode) in bytes' do
78
+ packet.flags = described_class::SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT
79
+ packet.tree_connect_request_extension.path = path
80
+ expect(packet.path_length).to eq(path.length * 2)
81
+ end
82
+ end
83
+
84
+ context 'when flags is not set to SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT' do
85
+ it 'should be the length of the full share path name (unicode) in bytes' do
86
+ packet.path = path
87
+ expect(packet.path_length).to eq(path.length * 2)
88
+ end
89
+ end
90
+ end
91
+
92
+ describe '#path' do
93
+ it 'should be a unicode string' do
94
+ expect(packet.path).to be_a RubySMB::Field::String16
95
+ end
96
+
97
+ it 'exists if #flags is not set to SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT' do
98
+ packet.flags = described_class::SMB2_TREE_CONNECT_FLAG_REDIRECT_TO_OWNER
99
+ expect(packet.path?).to be true
100
+ end
101
+ end
102
+
103
+ describe '#tree_connect_request_extension' do
104
+ it 'is a TreeConnectRequestExtension structure' do
105
+ expect(packet.tree_connect_request_extension).to be_a RubySMB::SMB2::Packet::TreeConnectRequestExtension
106
+ end
107
+
108
+ it 'exists if #flags is set to SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT' do
109
+ packet.flags = described_class::SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT
110
+ expect(packet.tree_connect_request_extension?).to be true
111
+ end
112
+ end
113
+
114
+ it 'reads binary data as expected' do
115
+ data = described_class.new
116
+ expect(described_class.read(data.to_binary_s)).to eq(data)
117
+ data = described_class.new(flags: described_class::SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT)
118
+ data.tree_connect_request_extension.tree_connect_contexts << RubySMB::SMB2::Packet::TreeConnectContext.new(context_type: 1)
119
+ expect(described_class.read(data.to_binary_s)).to eq(data)
120
+ end
121
+ end
122
+
123
+ RSpec.describe RubySMB::SMB2::Packet::TreeConnectRequestExtension do
124
+ subject(:packet) { described_class.new }
125
+
126
+ it { is_expected.to respond_to :tree_connect_context_offset }
127
+ it { is_expected.to respond_to :tree_connect_context_count }
128
+ it { is_expected.to respond_to :reserved }
129
+ it { is_expected.to respond_to :path }
130
+ it { is_expected.to respond_to :tree_connect_contexts }
131
+
132
+ it 'is little endian' do
133
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
134
+ end
135
+
136
+ describe '#tree_connect_context_offset' do
137
+ it 'is a 32-bit unsigned integer' do
138
+ expect(packet.tree_connect_context_offset).to be_a BinData::Uint32le
139
+ end
140
+
141
+ it 'is the offset from the start of the SMB2 TREE_CONNECT request of an array of tree connect contexts' do
142
+ tc = RubySMB::SMB2::Packet::TreeConnectRequest.new(
143
+ flags: RubySMB::SMB2::Packet::TreeConnectRequest::SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT
144
+ )
145
+ expect(tc.tree_connect_request_extension.tree_connect_context_offset).to eq(16)
146
+ end
147
+ end
148
+
149
+ describe '#tree_connect_context_count' do
150
+ it 'should be a 16-bit unsigned integer' do
151
+ expect(packet.tree_connect_context_count).to be_a BinData::Uint16le
152
+ end
153
+
154
+ it 'should be the #tree_connect_contexts size' do
155
+ packet.tree_connect_contexts << RubySMB::SMB2::Packet::TreeConnectContext.new(context_type: 1)
156
+ packet.tree_connect_contexts << RubySMB::SMB2::Packet::TreeConnectContext.new(context_type: 1)
157
+ expect(packet.tree_connect_context_count).to eq(2)
158
+ end
159
+ end
160
+
161
+ describe '#reserved' do
162
+ it 'should be a binary string' do
163
+ expect(packet.reserved).to be_a BinData::String
164
+ end
165
+
166
+ it 'is 10-bytes long' do
167
+ expect(packet.reserved.length).to eq(10)
168
+ end
169
+ end
170
+
171
+ describe '#path' do
172
+ it 'should be a unicode string' do
173
+ expect(packet.path).to be_a RubySMB::Field::String16
174
+ end
175
+ end
176
+
177
+ describe '#tree_connect_contexts' do
178
+ it 'is an Array field' do
179
+ expect(packet.tree_connect_contexts).to be_a BinData::Array
180
+ end
181
+
182
+ it 'has #tree_connect_context_count elements' do
183
+ packet.tree_connect_context_count = 3
184
+ expect(packet.tree_connect_contexts.size).to eq(3)
185
+ end
186
+ end
187
+
188
+ it 'reads binary data as expected' do
189
+ data = described_class.new
190
+ expect(described_class.read(data.to_binary_s)).to eq(data)
191
+ data.tree_connect_contexts << RubySMB::SMB2::Packet::TreeConnectContext.new(context_type: 1)
192
+ data.tree_connect_contexts << RubySMB::SMB2::Packet::TreeConnectContext.new(context_type: 1)
193
+ expect(described_class.read(data.to_binary_s)).to eq(data)
194
+ end
195
+ end
196
+
197
+ RSpec.describe RubySMB::SMB2::Packet::TreeConnectContext do
198
+ subject(:packet) do
199
+ described_class.new(
200
+ context_type: described_class::SMB2_REMOTED_IDENTITY_TREE_CONNECT_CONTEXT_ID
201
+ )
202
+ end
203
+
204
+ it { is_expected.to respond_to :context_type }
205
+ it { is_expected.to respond_to :data_length }
206
+ it { is_expected.to respond_to :reserved }
207
+ it { is_expected.to respond_to :data }
208
+
209
+ it 'is little endian' do
210
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
211
+ end
212
+
213
+ describe '#context_type' do
214
+ it 'should be a 16-bit unsigned integer' do
215
+ expect(packet.context_type).to be_a BinData::Uint16le
216
+ end
217
+ end
218
+
219
+ describe '#data_length' do
220
+ it 'should be a 16-bit unsigned integer' do
221
+ expect(packet.data_length).to be_a BinData::Uint16le
222
+ end
223
+
224
+ it 'is the length, in bytes, of the Data field' do
225
+ expect(packet.data_length).to eq(packet.data.to_binary_s.size)
226
+ end
227
+ end
228
+
229
+ describe '#reserved' do
230
+ it 'is a 32-bit unsigned integer' do
231
+ expect(packet.reserved).to be_a BinData::Uint32le
232
+ end
233
+ end
234
+
235
+ describe '#data' do
236
+ it 'is a BinData Choice' do
237
+ expect(packet.data).to be_a BinData::Choice
238
+ end
239
+
240
+ it 'contains the structure defined by #context_type' do
241
+ expect(packet.data).to eq(RubySMB::SMB2::Packet::RemotedIdentityTreeConnectContext.new)
242
+ end
243
+ end
244
+
245
+ it 'reads binary data as expected' do
246
+ data = described_class.new(context_type: 1)
247
+ expect(described_class.read(data.to_binary_s)).to eq(data)
248
+ end
249
+ end
250
+
251
+ RSpec.describe RubySMB::SMB2::Packet::RemotedIdentityTreeConnectContext do
252
+ subject(:packet) { described_class.new }
253
+
254
+ it { is_expected.to respond_to :ticket_type }
255
+ it { is_expected.to respond_to :ticket_size }
256
+ it { is_expected.to respond_to :user }
257
+ it { is_expected.to respond_to :user_name }
258
+ it { is_expected.to respond_to :domain }
259
+ it { is_expected.to respond_to :groups }
260
+ it { is_expected.to respond_to :restricted_groups }
261
+ it { is_expected.to respond_to :privileges }
262
+ it { is_expected.to respond_to :primary_group }
263
+ it { is_expected.to respond_to :owner }
264
+ it { is_expected.to respond_to :default_dacl }
265
+ it { is_expected.to respond_to :device_groups }
266
+ it { is_expected.to respond_to :user_claims }
267
+ it { is_expected.to respond_to :device_claims }
268
+ it { is_expected.to respond_to :ticket_info }
269
+
270
+ it 'is little endian' do
271
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
272
+ end
273
+
274
+ describe '#ticket_type' do
275
+ it 'should be a 16-bit unsigned integer' do
276
+ expect(packet.ticket_type).to be_a BinData::Uint16le
277
+ end
278
+
279
+ it 'should be 1' do
280
+ expect(packet.ticket_type).to eq(1)
281
+ end
282
+ end
283
+
284
+ describe '#ticket_size' do
285
+ it 'should be a 16-bit unsigned integer' do
286
+ expect(packet.ticket_size).to be_a BinData::Uint16le
287
+ end
288
+
289
+ it 'is the total size of this structure' do
290
+ packet.ticket_info = 'Ticket Info'
291
+ expect(packet.ticket_size).to eq(packet.num_bytes)
292
+ end
293
+ end
294
+
295
+ describe '#user' do
296
+ it 'should be a 16-bit unsigned integer' do
297
+ expect(packet.user).to be_a BinData::Uint16le
298
+ end
299
+ end
300
+
301
+ describe '#user_name' do
302
+ it 'should be a 16-bit unsigned integer' do
303
+ expect(packet.user_name).to be_a BinData::Uint16le
304
+ end
305
+ end
36
306
 
37
- it 'sets the path string to a UTF-16LE version of the supplied string' do
38
- packet.encode_path(path)
39
- expect(packet.path).to eq encoded_path
307
+ describe '#domain' do
308
+ it 'should be a 16-bit unsigned integer' do
309
+ expect(packet.domain).to be_a BinData::Uint16le
40
310
  end
311
+ end
41
312
 
42
- it 'updates the #path_length' do
43
- packet.encode_path(path)
44
- expect(packet.path_length).to eq encoded_path.length
313
+ describe '#groups' do
314
+ it 'should be a 16-bit unsigned integer' do
315
+ expect(packet.groups).to be_a BinData::Uint16le
45
316
  end
46
317
  end
318
+
319
+ describe '#restricted_groups' do
320
+ it 'should be a 16-bit unsigned integer' do
321
+ expect(packet.restricted_groups).to be_a BinData::Uint16le
322
+ end
323
+ end
324
+
325
+ describe '#privileges' do
326
+ it 'should be a 16-bit unsigned integer' do
327
+ expect(packet.privileges).to be_a BinData::Uint16le
328
+ end
329
+ end
330
+
331
+ describe '#primary_group' do
332
+ it 'should be a 16-bit unsigned integer' do
333
+ expect(packet.primary_group).to be_a BinData::Uint16le
334
+ end
335
+ end
336
+
337
+ describe '#owner' do
338
+ it 'should be a 16-bit unsigned integer' do
339
+ expect(packet.owner).to be_a BinData::Uint16le
340
+ end
341
+ end
342
+
343
+ describe '#default_dacl' do
344
+ it 'should be a 16-bit unsigned integer' do
345
+ expect(packet.default_dacl).to be_a BinData::Uint16le
346
+ end
347
+ end
348
+
349
+ describe '#device_groups' do
350
+ it 'should be a 16-bit unsigned integer' do
351
+ expect(packet.device_groups).to be_a BinData::Uint16le
352
+ end
353
+ end
354
+
355
+ describe '#user_claims' do
356
+ it 'should be a 16-bit unsigned integer' do
357
+ expect(packet.user_claims).to be_a BinData::Uint16le
358
+ end
359
+ end
360
+
361
+ describe '#device_claims' do
362
+ it 'should be a 16-bit unsigned integer' do
363
+ expect(packet.device_claims).to be_a BinData::Uint16le
364
+ end
365
+ end
366
+
367
+ describe '#ticket_info' do
368
+ it 'should be string' do
369
+ expect(packet.ticket_info).to be_a BinData::String
370
+ end
371
+ end
372
+
373
+ it 'reads binary data as expected' do
374
+ data = described_class.new(ticket_info: 'ticket info')
375
+ expect(described_class.read(data.to_binary_s)).to eq(data)
376
+ end
47
377
  end