ruby_smb 0.0.8 → 0.0.9

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 (60) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/examples/tree_connect.rb +27 -0
  5. data/lib/ruby_smb/client.rb +25 -2
  6. data/lib/ruby_smb/client/authentication.rb +5 -6
  7. data/lib/ruby_smb/client/negotiation.rb +2 -3
  8. data/lib/ruby_smb/client/signing.rb +6 -2
  9. data/lib/ruby_smb/client/tree_connect.rb +86 -0
  10. data/lib/ruby_smb/generic_packet.rb +1 -1
  11. data/lib/ruby_smb/smb1.rb +1 -1
  12. data/lib/ruby_smb/smb1/bit_field.rb +4 -0
  13. data/lib/ruby_smb/smb1/bit_field/directory_access_mask.rb +38 -0
  14. data/lib/ruby_smb/smb1/bit_field/file_access_mask.rb +38 -0
  15. data/lib/ruby_smb/smb1/bit_field/optional_support.rb +19 -0
  16. data/lib/ruby_smb/smb1/bit_field/tree_connect_flags.rb +18 -0
  17. data/lib/ruby_smb/smb1/commands.rb +2 -0
  18. data/lib/ruby_smb/smb1/packet.rb +4 -0
  19. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +34 -0
  20. data/lib/ruby_smb/smb1/packet/tree_connect_response.rb +74 -0
  21. data/lib/ruby_smb/smb1/packet/tree_disconnect_request.rb +29 -0
  22. data/lib/ruby_smb/smb1/packet/tree_disconnect_response.rb +30 -0
  23. data/lib/ruby_smb/smb1/tree.rb +55 -0
  24. data/lib/ruby_smb/smb2.rb +1 -0
  25. data/lib/ruby_smb/smb2/bit_field.rb +4 -0
  26. data/lib/ruby_smb/smb2/bit_field/directory_access_mask.rb +38 -0
  27. data/lib/ruby_smb/smb2/bit_field/file_access_mask.rb +38 -0
  28. data/lib/ruby_smb/smb2/bit_field/share_capabailities.rb +21 -0
  29. data/lib/ruby_smb/smb2/bit_field/share_flags.rb +75 -0
  30. data/lib/ruby_smb/smb2/packet.rb +5 -0
  31. data/lib/ruby_smb/smb2/packet/error_packet.rb +15 -0
  32. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +27 -0
  33. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +50 -0
  34. data/lib/ruby_smb/smb2/packet/tree_disconnect_request.rb +21 -0
  35. data/lib/ruby_smb/smb2/packet/tree_disconnect_response.rb +22 -0
  36. data/lib/ruby_smb/smb2/smb2_header.rb +3 -3
  37. data/lib/ruby_smb/smb2/tree.rb +51 -0
  38. data/lib/ruby_smb/version.rb +1 -1
  39. data/ruby_smb.gemspec +1 -1
  40. data/spec/lib/ruby_smb/client_spec.rb +114 -4
  41. data/spec/lib/ruby_smb/smb1/bit_field/directory_access_mask_spec.rb +190 -0
  42. data/spec/lib/ruby_smb/smb1/bit_field/file_access_mask_spec.rb +190 -0
  43. data/spec/lib/ruby_smb/smb1/bit_field/optional_support_spec.rb +52 -0
  44. data/spec/lib/ruby_smb/smb1/bit_field/tree_connect_flags_spec.rb +37 -0
  45. data/spec/lib/ruby_smb/smb1/packet/tree_connect_request_spec.rb +53 -0
  46. data/spec/lib/ruby_smb/smb1/packet/tree_connect_response_spec.rb +86 -0
  47. data/spec/lib/ruby_smb/smb1/packet/tree_disconnect_request_spec.rb +40 -0
  48. data/spec/lib/ruby_smb/smb1/packet/tree_disconnect_response_spec.rb +40 -0
  49. data/spec/lib/ruby_smb/smb1/tree_spec.rb +55 -0
  50. data/spec/lib/ruby_smb/smb2/bit_field/directory_access_mask_spec.rb +190 -0
  51. data/spec/lib/ruby_smb/smb2/bit_field/file_access_mask_spec.rb +190 -0
  52. data/spec/lib/ruby_smb/smb2/bit_field/share_capabilities_spec.rb +54 -0
  53. data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +184 -0
  54. data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +49 -0
  55. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +57 -0
  56. data/spec/lib/ruby_smb/smb2/packet/tree_disconnect_request_spec.rb +30 -0
  57. data/spec/lib/ruby_smb/smb2/packet/tree_disconnect_response_spec.rb +30 -0
  58. data/spec/lib/ruby_smb/smb2/tree_spec.rb +54 -0
  59. metadata +63 -6
  60. metadata.gz.sig +0 -0
@@ -0,0 +1,54 @@
1
+ RSpec.describe RubySMB::SMB2::BitField::ShareCapabilities do
2
+ subject(:capabilities) { described_class.new }
3
+
4
+ it { is_expected.to respond_to :asymmetric }
5
+ it { is_expected.to respond_to :cluster }
6
+ it { is_expected.to respond_to :scaleout }
7
+ it { is_expected.to respond_to :continuous }
8
+ it { is_expected.to respond_to :dfs }
9
+
10
+
11
+ it 'is little endian' do
12
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
13
+ end
14
+
15
+ describe '#dfs' do
16
+ it 'is a 1-bit flag' do
17
+ expect(capabilities.dfs).to be_a BinData::Bit1
18
+ end
19
+
20
+ it_behaves_like 'bit field with one flag set', :dfs, 'V', 0x00000008
21
+ end
22
+
23
+ describe '#continuous' do
24
+ it 'is a 1-bit flag' do
25
+ expect(capabilities.continuous).to be_a BinData::Bit1
26
+ end
27
+
28
+ it_behaves_like 'bit field with one flag set', :continuous, 'V', 0x00000010
29
+ end
30
+
31
+ describe '#scaleout' do
32
+ it 'is a 1-bit flag' do
33
+ expect(capabilities.scaleout).to be_a BinData::Bit1
34
+ end
35
+
36
+ it_behaves_like 'bit field with one flag set', :scaleout, 'V', 0x00000020
37
+ end
38
+
39
+ describe '#cluster' do
40
+ it 'is a 1-bit flag' do
41
+ expect(capabilities.cluster).to be_a BinData::Bit1
42
+ end
43
+
44
+ it_behaves_like 'bit field with one flag set', :cluster, 'V', 0x00000040
45
+ end
46
+
47
+ describe '#asymmetric' do
48
+ it 'is a 1-bit flag' do
49
+ expect(capabilities.asymmetric).to be_a BinData::Bit1
50
+ end
51
+
52
+ it_behaves_like 'bit field with one flag set', :asymmetric, 'V', 0x00000080
53
+ end
54
+ end
@@ -0,0 +1,184 @@
1
+ RSpec.describe RubySMB::SMB2::BitField::ShareFlags do
2
+ subject(:flags) { described_class.new }
3
+
4
+ it { is_expected.to respond_to :dfs_root }
5
+ it { is_expected.to respond_to :dfs }
6
+ it { is_expected.to respond_to :encrypt }
7
+ it { is_expected.to respond_to :hash_v2 }
8
+ it { is_expected.to respond_to :hash_v1 }
9
+ it { is_expected.to respond_to :force_oplock }
10
+ it { is_expected.to respond_to :access_based_enum }
11
+ it { is_expected.to respond_to :namespace_caching }
12
+ it { is_expected.to respond_to :shared_delete }
13
+ it { is_expected.to respond_to :restrict_exclusive_opens }
14
+
15
+ it 'is little endian' do
16
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
17
+ end
18
+
19
+ describe '#dfs' do
20
+ it 'should be a 1-bit field per the SMB spec' do
21
+ expect(flags.dfs).to be_a BinData::Bit1
22
+ end
23
+
24
+ it_behaves_like 'bit field with one flag set', :dfs, 'V', 0x00000001
25
+ end
26
+
27
+
28
+ describe '#dfs_root' do
29
+ it 'should be a 1-bit field per the SMB spec' do
30
+ expect(flags.dfs_root).to be_a BinData::Bit1
31
+ end
32
+
33
+ it_behaves_like 'bit field with one flag set', :dfs_root, 'V', 0x00000002
34
+ end
35
+
36
+ describe '#restrict_exclusive_opens' do
37
+ it 'should be a 1-bit field per the SMB spec' do
38
+ expect(flags.restrict_exclusive_opens).to be_a BinData::Bit1
39
+ end
40
+
41
+ it_behaves_like 'bit field with one flag set', :restrict_exclusive_opens, 'V', 0x00000100
42
+ end
43
+
44
+ describe '#shared_delete' do
45
+ it 'should be a 1-bit field per the SMB spec' do
46
+ expect(flags.shared_delete).to be_a BinData::Bit1
47
+ end
48
+
49
+ it_behaves_like 'bit field with one flag set', :shared_delete, 'V', 0x00000200
50
+ end
51
+
52
+ describe '#namespace_caching' do
53
+ it 'should be a 1-bit field per the SMB spec' do
54
+ expect(flags.namespace_caching).to be_a BinData::Bit1
55
+ end
56
+
57
+ it_behaves_like 'bit field with one flag set', :namespace_caching, 'V', 0x00000400
58
+ end
59
+
60
+ describe '#access_based_enum' do
61
+ it 'should be a 1-bit field per the SMB spec' do
62
+ expect(flags.access_based_enum).to be_a BinData::Bit1
63
+ end
64
+
65
+ it_behaves_like 'bit field with one flag set', :access_based_enum, 'V', 0x00000800
66
+ end
67
+
68
+ describe '#force_oplock' do
69
+ it 'should be a 1-bit field per the SMB spec' do
70
+ expect(flags.force_oplock).to be_a BinData::Bit1
71
+ end
72
+
73
+ it_behaves_like 'bit field with one flag set', :force_oplock, 'V', 0x00001000
74
+ end
75
+
76
+ describe '#hash_v1' do
77
+ it 'should be a 1-bit field per the SMB spec' do
78
+ expect(flags.hash_v1).to be_a BinData::Bit1
79
+ end
80
+
81
+ it_behaves_like 'bit field with one flag set', :hash_v1, 'V', 0x00002000
82
+ end
83
+
84
+ describe '#hash_v2' do
85
+ it 'should be a 1-bit field per the SMB spec' do
86
+ expect(flags.hash_v2).to be_a BinData::Bit1
87
+ end
88
+
89
+ it_behaves_like 'bit field with one flag set', :hash_v2, 'V', 0x00004000
90
+ end
91
+
92
+ describe '#encrypt' do
93
+ it 'should be a 1-bit field per the SMB spec' do
94
+ expect(flags.encrypt).to be_a BinData::Bit1
95
+ end
96
+
97
+ it_behaves_like 'bit field with one flag set', :encrypt, 'V', 0x00008000
98
+ end
99
+
100
+ describe '#set_manual_caching' do
101
+ it 'turns off the caching bits' do
102
+ flags.set_manual_caching
103
+ expect(flags.vdo_caching).to eq 0
104
+ expect(flags.auto_caching).to eq 0
105
+ end
106
+
107
+ it 'has a value of 0x00000000' do
108
+ flags.set_manual_caching
109
+ flag_val = flags.to_binary_s
110
+ flag_val = flag_val.unpack('V').first
111
+ expect(flag_val).to eq 0x00000000
112
+ end
113
+ end
114
+
115
+ describe '#set_auto_caching' do
116
+ it 'turns on the auto_caching bit' do
117
+ flags.set_auto_caching
118
+ expect(flags.vdo_caching).to eq 0
119
+ expect(flags.auto_caching).to eq 1
120
+ end
121
+
122
+ it 'has a value of 0x00000010' do
123
+ flags.set_auto_caching
124
+ flag_val = flags.to_binary_s
125
+ flag_val = flag_val.unpack('V').first
126
+ expect(flag_val).to eq 0x00000010
127
+ end
128
+ end
129
+
130
+ describe '#set_vdo_caching' do
131
+ it 'turns on the vdo_caching bits' do
132
+ flags.set_vdo_caching
133
+ expect(flags.vdo_caching).to eq 1
134
+ expect(flags.auto_caching).to eq 0
135
+ end
136
+
137
+ it 'has a value of 0x00000020' do
138
+ flags.set_vdo_caching
139
+ flag_val = flags.to_binary_s
140
+ flag_val = flag_val.unpack('V').first
141
+ expect(flag_val).to eq 0x00000020
142
+ end
143
+ end
144
+
145
+ describe '#set_no_caching' do
146
+ it 'turns on all the caching bits' do
147
+ flags.set_no_caching
148
+ expect(flags.vdo_caching).to eq 1
149
+ expect(flags.auto_caching).to eq 1
150
+ end
151
+
152
+ it 'has a value of 0x00000030' do
153
+ flags.set_no_caching
154
+ flag_val = flags.to_binary_s
155
+ flag_val = flag_val.unpack('V').first
156
+ expect(flag_val).to eq 0x00000030
157
+ end
158
+ end
159
+
160
+ describe '#caching_type' do
161
+ it 'returns VDO if only that flag is set' do
162
+ flags.vdo_caching = 1
163
+ expect(flags.caching_type).to eq :vdo
164
+ end
165
+
166
+ it 'returns Auto if only that flag is set' do
167
+ flags.auto_caching = 1
168
+ expect(flags.caching_type).to eq :auto
169
+ end
170
+
171
+ it 'returns No Caching if both caching flags are set' do
172
+ flags.auto_caching = 1
173
+ flags.vdo_caching = 1
174
+ expect(flags.caching_type).to eq :no_caching
175
+ end
176
+
177
+ it 'returns Manual if neither caching flags are set' do
178
+ flags.auto_caching = 0
179
+ flags.vdo_caching = 0
180
+ expect(flags.caching_type).to eq :manual
181
+ end
182
+
183
+ end
184
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RubySMB::SMB2::Packet::TreeConnectRequest do
4
+
5
+ subject(:packet) { described_class.new }
6
+
7
+ it { is_expected.to respond_to :smb2_header }
8
+ it { is_expected.to respond_to :structure_size }
9
+ it { is_expected.to respond_to :flags }
10
+ it { is_expected.to respond_to :path_offset }
11
+ it { is_expected.to respond_to :path_length }
12
+ it { is_expected.to respond_to :path }
13
+
14
+
15
+ it 'is little endian' do
16
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
17
+ end
18
+
19
+ describe '#smb2_header' do
20
+ subject(:header) { packet.smb2_header }
21
+
22
+ it 'is a standard SMB Header' do
23
+ expect(header).to be_a RubySMB::SMB2::SMB2Header
24
+ end
25
+
26
+ it 'should have the command set to SMB_COM_NEGOTIATE' do
27
+ expect(header.command).to eq RubySMB::SMB2::Commands::TREE_CONNECT
28
+ end
29
+
30
+ it 'should not have the response flag set' do
31
+ expect(header.flags.reply).to eq 0
32
+ end
33
+ end
34
+
35
+ describe '#encode_path' do
36
+ let(:path) { "\\192.168.1.1\\example" }
37
+ let(:encoded_path) { path.encode("utf-16le").force_encoding("binary") }
38
+
39
+ it 'sets the path string to a UTF-16LE version of the supplied string' do
40
+ packet.encode_path(path)
41
+ expect(packet.path).to eq encoded_path
42
+ end
43
+
44
+ it 'updates the #path_length' do
45
+ packet.encode_path(path)
46
+ expect(packet.path_length).to eq encoded_path.length
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RubySMB::SMB2::Packet::TreeConnectResponse do
4
+
5
+ subject(:packet) { described_class.new }
6
+
7
+ it { is_expected.to respond_to :smb2_header }
8
+ it { is_expected.to respond_to :structure_size }
9
+ it { is_expected.to respond_to :share_type }
10
+ it { is_expected.to respond_to :share_flags }
11
+ it { is_expected.to respond_to :capabilities }
12
+ it { is_expected.to respond_to :maximal_access }
13
+
14
+ it 'is little endian' do
15
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
16
+ end
17
+
18
+ describe '#smb2_header' do
19
+ subject(:header) { packet.smb2_header }
20
+
21
+ it 'is a standard SMB Header' do
22
+ expect(header).to be_a RubySMB::SMB2::SMB2Header
23
+ end
24
+
25
+ it 'should have the command set to SMB_COM_NEGOTIATE' do
26
+ expect(header.command).to eq RubySMB::SMB2::Commands::TREE_CONNECT
27
+ end
28
+
29
+ it 'should have the response flag set' do
30
+ expect(header.flags.reply).to eq 1
31
+ end
32
+ end
33
+
34
+ describe '#is_directory?' do
35
+ it 'returns true if #share_type is 0x01' do
36
+ packet.share_type = 0x01
37
+ expect(packet.is_directory?).to be true
38
+ end
39
+
40
+ it 'returns false if #share_type is not 0x01' do
41
+ packet.share_type = 0x02
42
+ expect(packet.is_directory?).to be false
43
+ end
44
+ end
45
+
46
+ describe '#access_rights' do
47
+ it 'is a DirectoryAccessMask if the Tree is a directory' do
48
+ allow(packet).to receive(:is_directory?).and_return(true)
49
+ expect(packet.access_rights).to be_a RubySMB::SMB2::BitField::DirectoryAccessMask
50
+ end
51
+
52
+ it 'is a FileAccessMask if the Tree is not a directory' do
53
+ allow(packet).to receive(:is_directory?).and_return(false)
54
+ expect(packet.access_rights).to be_a RubySMB::SMB2::BitField::FileAccessMask
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RubySMB::SMB2::Packet::TreeDisconnectRequest do
4
+
5
+ subject(:packet) { described_class.new }
6
+
7
+ it { is_expected.to respond_to :smb2_header }
8
+ it { is_expected.to respond_to :structure_size }
9
+
10
+ it 'is little endian' do
11
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
12
+ end
13
+
14
+ describe '#smb2_header' do
15
+ subject(:header) { packet.smb2_header }
16
+
17
+ it 'is a standard SMB Header' do
18
+ expect(header).to be_a RubySMB::SMB2::SMB2Header
19
+ end
20
+
21
+ it 'should have the command set to SMB_COM_NEGOTIATE' do
22
+ expect(header.command).to eq RubySMB::SMB2::Commands::TREE_DISCONNECT
23
+ end
24
+
25
+ it 'should not have the response flag set' do
26
+ expect(header.flags.reply).to eq 0
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RubySMB::SMB2::Packet::TreeDisconnectResponse do
4
+
5
+ subject(:packet) { described_class.new }
6
+
7
+ it { is_expected.to respond_to :smb2_header }
8
+ it { is_expected.to respond_to :structure_size }
9
+
10
+ it 'is little endian' do
11
+ expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
12
+ end
13
+
14
+ describe '#smb2_header' do
15
+ subject(:header) { packet.smb2_header }
16
+
17
+ it 'is a standard SMB Header' do
18
+ expect(header).to be_a RubySMB::SMB2::SMB2Header
19
+ end
20
+
21
+ it 'should have the command set to SMB_COM_NEGOTIATE' do
22
+ expect(header.command).to eq RubySMB::SMB2::Commands::TREE_DISCONNECT
23
+ end
24
+
25
+ it 'should have the response flag set' do
26
+ expect(header.flags.reply).to eq 1
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RubySMB::SMB2::Tree do
4
+ let(:dispatcher) { RubySMB::Dispatcher::Socket.new(nil) }
5
+
6
+ let(:client) { RubySMB::Client.new(dispatcher, username: 'msfadmin', password: 'msfadmin') }
7
+ let(:tree_id) { 2049 }
8
+ let(:path) { '\\192.168.1.1\example' }
9
+ let(:response) {
10
+ packet = RubySMB::SMB2::Packet::TreeConnectResponse.new
11
+ packet.smb2_header.tree_id = tree_id
12
+ packet.maximal_access.read("\xff\x01\x1f\x00")
13
+ packet.share_type = 0x01
14
+ packet
15
+ }
16
+ subject(:tree) {
17
+ described_class.new(client:client, share:path, response:response )
18
+ }
19
+
20
+ it { is_expected.to respond_to :client }
21
+ it { is_expected.to respond_to :permissions }
22
+ it { is_expected.to respond_to :share }
23
+ it { is_expected.to respond_to :id }
24
+
25
+ it 'inherits the client that spawned it' do
26
+ expect(tree.client).to eq client
27
+ end
28
+
29
+ it 'inherits the permissions from the response packet' do
30
+ expect(tree.permissions).to eq response.maximal_access
31
+ end
32
+
33
+ it 'inherits the Tree id from the response packet' do
34
+ expect(tree.id).to eq response.smb2_header.tree_id
35
+ end
36
+
37
+ describe '#disconnect!' do
38
+ let(:disco_req) { RubySMB::SMB2::Packet::TreeDisconnectRequest.new }
39
+ let(:disco_resp) { RubySMB::SMB2::Packet::TreeDisconnectResponse.new }
40
+
41
+ it 'sends a TreeDisconnectRequest with the Tree ID in the header' do
42
+ allow(RubySMB::SMB2::Packet::TreeDisconnectRequest).to receive(:new).and_return(disco_req)
43
+ modified_req = disco_req
44
+ modified_req.smb2_header.tree_id = tree.id
45
+ expect(client).to receive(:send_recv).with(modified_req).and_return(disco_resp.to_binary_s)
46
+ tree.disconnect!
47
+ end
48
+
49
+ it 'returns the NTStatus code from the response' do
50
+ allow(client).to receive(:send_recv).and_return(disco_resp.to_binary_s)
51
+ expect(tree.disconnect!).to eq disco_resp.status_code
52
+ end
53
+ end
54
+ end