ruby_smb 1.1.0 → 2.0.0

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 (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
@@ -30,6 +30,8 @@ module RubySMB
30
30
  require 'ruby_smb/smb2/packet/write_response'
31
31
  require 'ruby_smb/smb2/packet/ioctl_request'
32
32
  require 'ruby_smb/smb2/packet/ioctl_response'
33
+ require 'ruby_smb/smb2/packet/transform_header'
34
+ require 'ruby_smb/smb2/packet/compression_transform_header'
33
35
  end
34
36
  end
35
37
  end
@@ -0,0 +1,41 @@
1
+ module RubySMB
2
+ module SMB2
3
+ module Packet
4
+ # An SMB2 COMPRESSION_TRANSFORM_HEADER Packet as defined in
5
+ # [2.2.42 SMB2 COMPRESSION_TRANSFORM_HEADER](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/1d435f21-9a21-4f4c-828e-624a176cf2a0)
6
+ class CompressionTransformHeader < BinData::Record
7
+ endian :little
8
+
9
+ bit32 :protocol, label: 'Protocol ID Field', initial_value: 0xFC534D42
10
+ uint32 :original_compressed_segment_size, label: 'Original Compressed Segment Size'
11
+ uint16 :compression_algorithm, label: 'Compression Algorithm'
12
+ uint16 :flags, label: 'Flags'
13
+ uint32 :offset, label: 'Offset / Length'
14
+ end
15
+
16
+ # An SMB2 SMB2_COMPRESSION_TRANSFORM_HEADER_PAYLOAD Packet as defined in
17
+ # [2.2.42.1 SMB2_COMPRESSION_TRANSFORM_HEADER_PAYLOAD](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/8898e8e7-f1b2-47f5-a525-2ce5bad6db64)
18
+ class Smb2CompressionPayloadHeader < BinData::Record
19
+ endian :little
20
+ hide :reserved
21
+
22
+ uint16 :algorithm_id, label: 'Algorithm ID'
23
+ uint16 :reserved
24
+ uint32 :payload_length, label: 'Compressed Payload Length'
25
+ end
26
+
27
+ # An SMB2 SMB2_COMPRESSION_PATTERN_PAYLOAD_V1 Packet as defined in
28
+ # [2.2.42.2 SMB2_COMPRESSION_PATTERN_PAYLOAD_V1](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/f6859837-395a-4d0a-8971-1fc3919e2d09)
29
+ class Smb2CompressionPatternPayloadV1 < BinData::Record
30
+ endian :little
31
+ hide :reserved1, :reserved2
32
+
33
+ uint8 :pattern, label: 'Pattern'
34
+ uint8 :reserved1
35
+ uint16 :reserved2
36
+ uint32 :repetitions, label: 'Repetitions'
37
+ end
38
+ end
39
+ end
40
+ end
41
+
@@ -1,3 +1,5 @@
1
+ require 'ruby_smb/smb2/negotiate_context'
2
+
1
3
  module RubySMB
2
4
  module SMB2
3
5
  module Packet
@@ -8,23 +10,30 @@ module RubySMB
8
10
 
9
11
  endian :little
10
12
  smb2_header :smb2_header
11
- uint16 :structure_size, label: 'Structure Size', initial_value: 36
12
- uint16 :dialect_count, label: 'Dialect Count'
13
- smb2_security_mode :security_mode
14
- uint16 :reserved1, label: 'Reserved', initial_value: 0
15
- smb2_capabilities :capabilities
16
- string :client_guid, label: 'Client GUID', length: 16
17
- file_time :client_start_time, label: 'Client Start Time', initial_value: 0
18
- array :dialects, label: 'Dialects', type: :uint16, read_until: :eof
19
-
20
- # Adds a dialect to the Dialects array and increments the dialect count
13
+ uint16 :structure_size, label: 'Structure Size', initial_value: 36
14
+ uint16 :dialect_count, label: 'Dialect Count', initial_value: -> { dialects.size }
15
+ smb2_security_mode :security_mode, label: 'Security Mode'
16
+ uint16 :reserved1, label: 'Reserved', initial_value: 0
17
+ smb2_capabilities :capabilities, label: 'Capabilities'
18
+ string :client_guid, label: 'Client GUID', length: 16
19
+ struct :negotiate_context_info, label: 'Negotiate Context Info', onlyif: -> { has_negotiate_context? } do
20
+ uint32 :negotiate_context_offset, label: 'Negotiate Context Offset', initial_value: -> { negotiate_context_list.abs_offset }
21
+ uint16 :negotiate_context_count, label: 'Negotiate Context Count', initial_value: -> { negotiate_context_list.size }
22
+ uint16 :reserved2, label: 'Reserved', initial_value: 0
23
+ end
24
+ file_time :client_start_time, label: 'Client Start Time', initial_value: 0, onlyif: -> { !has_negotiate_context? }
25
+ array :dialects, label: 'Dialects', type: :uint16, initial_length: -> { dialect_count }
26
+ string :pad, label: 'Padding', length: -> { pad_length(self.dialects) }, onlyif: -> { has_negotiate_context? }
27
+ array :negotiate_context_list, label: 'Negotiate Context List', type: :negotiate_context, onlyif: -> { has_negotiate_context? }, read_until: :eof
28
+
29
+ # Adds a dialect to the Dialects array
21
30
  #
22
31
  # @param [Fixnum] the numeric code for the dialect you wish to add
23
32
  # @return [Array<Fixnum>] the array of all currently selected dialects
33
+ # @raise [ArgumentError] if the dialect is not an Integer
24
34
  def add_dialect(dialect)
25
- return ArgumentError, 'Must be a number' unless dialect.is_a? Integer
26
- self.dialect_count += 1
27
- dialects << dialect
35
+ raise ArgumentError, 'Must be a number' unless dialect.is_a? Integer
36
+ self.dialects << dialect
28
37
  end
29
38
 
30
39
  # Takes an array of dialects and sets it on the packet. Also updates
@@ -35,12 +44,40 @@ module RubySMB
35
44
  # @return [Array<Fixnum>] the current value of the dialects array
36
45
  def set_dialects(add_dialects = [])
37
46
  self.dialects = []
38
- self.dialect_count = 0
39
47
  add_dialects.each do |dialect|
40
48
  add_dialect(dialect)
41
49
  end
42
50
  dialects
43
51
  end
52
+
53
+ # Adds a Negotiate Context to the #negotiate_context_list
54
+ #
55
+ # @param [NegotiateContext] the Negotiate Context structure you wish to add
56
+ # @return [Array<Fixnum>] the array of all currently added Negotiate Contexts
57
+ # @raise [ArgumentError] if the dialect is not a NegotiateContext structure
58
+ def add_negotiate_context(nc)
59
+ raise ArgumentError, 'Must be a NegotiateContext' unless nc.is_a? NegotiateContext
60
+ previous_element = negotiate_context_list.last || negotiate_context_list
61
+ pad_length = pad_length(previous_element)
62
+ self.negotiate_context_list << nc
63
+ self.negotiate_context_list.last.pad = "\x00" * pad_length
64
+ self.negotiate_context_list
65
+ end
66
+
67
+
68
+ private
69
+
70
+ # Determines the correct length for the padding, so that the next
71
+ # field is 8-byte aligned.
72
+ def pad_length(prev_element)
73
+ offset = (prev_element.abs_offset + prev_element.to_binary_s.length) % 8
74
+ (8 - offset) % 8
75
+ end
76
+
77
+ # Return true if the dialect version requires Negotiate Contexts
78
+ def has_negotiate_context?
79
+ dialects.any? { |dialect| dialect.to_i == 0x0311 }
80
+ end
44
81
  end
45
82
  end
46
83
  end
@@ -1,3 +1,5 @@
1
+ require 'ruby_smb/smb2/negotiate_context'
2
+
1
3
  module RubySMB
2
4
  module SMB2
3
5
  module Packet
@@ -8,10 +10,11 @@ module RubySMB
8
10
 
9
11
  endian :little
10
12
  smb2_header :smb2_header
11
- uint16 :structure_size, label: 'Structure Size', initial_value: 65
13
+ uint16 :structure_size, label: 'Structure Size', initial_value: 65
12
14
  smb2_security_mode :security_mode
13
15
  uint16 :dialect_revision, label: 'Dialect Revision'
14
- uint16 :negotiate_context_count, label: 'Negotiate Context Count', initial_value: 0
16
+ uint16 :negotiate_context_count, label: 'Negotiate Context Count', initial_value: -> { negotiate_context_list.size }, onlyif: -> { has_negotiate_context? }
17
+ uint16 :reserved1, label: 'Reserved', initial_value: 0, onlyif: -> { !has_negotiate_context? }
15
18
  string :server_guid, label: 'Server GUID', length: 16
16
19
  smb2_capabilities :capabilities
17
20
  uint32 :max_transact_size, label: 'Max Transaction Size'
@@ -21,13 +24,56 @@ module RubySMB
21
24
  file_time :server_start_time, label: 'Server Start Time'
22
25
  uint16 :security_buffer_offset, label: 'Offset to Security Buffer'
23
26
  uint16 :security_buffer_length, label: 'Security Buffer Length', initial_value: -> { security_buffer.length }
24
- uint32 :negotiate_context_offset, label: 'Offset to Negotiate Context'
27
+ uint32 :negotiate_context_offset, label: 'Offset to Negotiate Context', onlyif: -> { has_negotiate_context? }
28
+ uint32 :reserved2, label: 'Reserved', initial_value: 0, onlyif: -> { !has_negotiate_context? }
25
29
  string :security_buffer, label: 'Security Buffer', read_length: :security_buffer_length
30
+ string :pad, label: 'Padding', length: -> { pad_length(self.security_buffer) }, onlyif: -> { has_negotiate_context? }
31
+ array :negotiate_context_list, label: 'Negotiate Context List', initial_length: -> { negotiate_context_count }, type: :negotiate_context, onlyif: -> { has_negotiate_context? }
26
32
 
27
33
  def initialize_instance
28
34
  super
29
35
  smb2_header.flags.reply = 1
30
36
  end
37
+
38
+ # Find the first Negotiate Context structure that matches the given
39
+ # context type
40
+ #
41
+ # @param [Integer] the Negotiate Context structure you wish to add
42
+ # @return [NegotiateContext] the Negotiate Context structure or nil if
43
+ # not found
44
+ def find_negotiate_context(type)
45
+ negotiate_context_list.find { |nc| nc.context_type == type }
46
+ end
47
+
48
+ # Adds a Negotiate Context to the #negotiate_context_list
49
+ #
50
+ # @param [NegotiateContext] the Negotiate Context structure you wish to add
51
+ # @return [Array<Fixnum>] the array of all currently added Negotiate Contexts
52
+ # @raise [ArgumentError] if the dialect is not a NegotiateContext structure
53
+ def add_negotiate_context(nc)
54
+ raise ArgumentError, 'Must be a NegotiateContext' unless nc.is_a? NegotiateContext
55
+ previous_element = negotiate_context_list.last || negotiate_context_list
56
+ pad_length = pad_length(previous_element)
57
+ self.negotiate_context_list << nc
58
+ self.negotiate_context_list.last.pad = "\x00" * pad_length
59
+ self.negotiate_context_list
60
+ end
61
+
62
+
63
+ private
64
+
65
+ # Determines the correct length for the padding, so that the next
66
+ # field is 8-byte aligned.
67
+ def pad_length(prev_element)
68
+ offset = (prev_element.abs_offset + prev_element.to_binary_s.length) % 8
69
+ (8 - offset) % 8
70
+ end
71
+
72
+ # Return true if the dialect version requires Negotiate Contexts
73
+ def has_negotiate_context?
74
+ dialect_revision == 0x0311
75
+ end
76
+
31
77
  end
32
78
  end
33
79
  end
@@ -0,0 +1,84 @@
1
+ module RubySMB
2
+ module SMB2
3
+ module Packet
4
+ # An SMB2 TRANSFORM_HEADER Packet as defined in
5
+ # [2.2.41 SMB2 TRANSFORM_HEADER](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/d6ce2327-a4c9-4793-be66-7b5bad2175fa)
6
+ class TransformHeader < BinData::Record
7
+ endian :little
8
+ hide :reserved0
9
+
10
+ endian :little
11
+ bit32 :protocol, label: 'Protocol ID Field', initial_value: 0xFD534D42
12
+ string :signature, label: 'Signature', length: 16
13
+ string :nonce, label: 'Nonce', length: 16
14
+ uint32 :original_message_size, label: 'Original Message Size'
15
+ uint16 :reserved0
16
+ uint16 :flags, label: 'Flags / Encryption Algorithm'
17
+ uint64 :session_id, label: 'Session ID'
18
+ array :encrypted_data, label: 'Encrypted Data', type: :uint8, read_until: :eof
19
+
20
+ def decrypt(key, algorithm: 'AES-128-GCM')
21
+ auth_data = self.to_binary_s[20...52]
22
+ encrypted_data = self.encrypted_data.to_ary.pack('C*')
23
+
24
+ case algorithm
25
+ when 'AES-128-CCM'
26
+ cipher = OpenSSL::CCM.new('AES', key, 16)
27
+ unencrypted_data = cipher.decrypt(encrypted_data + self.signature, self.nonce[0...11], auth_data)
28
+ unless unencrypted_data.length > 0
29
+ raise OpenSSL::Cipher::CipherError # raised for consistency with GCM mode
30
+ end
31
+ when 'AES-128-GCM'
32
+ cipher = OpenSSL::Cipher.new(algorithm).decrypt
33
+ cipher.key = key
34
+ cipher.iv = self.nonce[0...12]
35
+ cipher.auth_data = auth_data
36
+ cipher.auth_tag = self.signature
37
+ unencrypted_data = cipher.update(encrypted_data)
38
+ cipher.final # raises OpenSSL::Cipher::CipherError on signature failure
39
+ else
40
+ raise ArgumentError.new('Invalid algorithm, must be either AES-128-CCM or AES-128-GCM')
41
+ end
42
+
43
+ unencrypted_data[0...self.original_message_size]
44
+ rescue Exception => e
45
+ raise RubySMB::Error::EncryptionError, "Error while decrypting with '#{algorithm}' (#{e.class}: #{e})"
46
+ end
47
+
48
+ def encrypt(unencrypted_data, key, algorithm: 'AES-128-GCM')
49
+ if unencrypted_data.is_a? BinData::Record
50
+ unencrypted_data = unencrypted_data.to_binary_s
51
+ end
52
+
53
+ self.original_message_size.assign(unencrypted_data.length)
54
+
55
+ case algorithm
56
+ when 'AES-128-CCM'
57
+ cipher = OpenSSL::CCM.new('AES', key, 16)
58
+ random_iv = OpenSSL::Random.random_bytes(11)
59
+ self.nonce.assign(random_iv)
60
+ result = cipher.encrypt(unencrypted_data, random_iv, self.to_binary_s[20...52])
61
+ encrypted_data = result[0...-16]
62
+ auth_tag = result[-16..-1]
63
+ when 'AES-128-GCM'
64
+ cipher = OpenSSL::Cipher.new(algorithm).encrypt
65
+ cipher.iv_len = 12
66
+ cipher.key = key
67
+ self.nonce.assign(cipher.random_iv)
68
+ cipher.auth_data = self.to_binary_s[20...52]
69
+ encrypted_data = cipher.update(unencrypted_data) + cipher.final
70
+ auth_tag = cipher.auth_tag
71
+ else
72
+ raise ArgumentError.new('Invalid algorithm, must be either AES-128-CCM or AES-128-GCM')
73
+ end
74
+
75
+ self.encrypted_data.assign(encrypted_data.bytes)
76
+ self.signature.assign(auth_tag)
77
+ nil
78
+ rescue Exception => e
79
+ raise RubySMB::Error::EncryptionError, "Error while encrypting with '#{algorithm}' (#{e.class}: #{e})"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -1,22 +1,108 @@
1
1
  module RubySMB
2
2
  module SMB2
3
3
  module Packet
4
+
5
+
6
+ # An SMB2 RemotedIdentityTreeConnectContext Packet as defined in
7
+ # [2.2.9.2.1 SMB2_REMOTED_IDENTITY_TREE_CONNECT Context](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/ee7ff411-93e0-484f-9f73-31916fee4cb8)
8
+ # TODO: implement helper methods to add each Remote Identity element
9
+ class RemotedIdentityTreeConnectContext < BinData::Record
10
+ endian :little
11
+ uint16 :ticket_type, label: 'Ticket Type', initial_value: 0x0001
12
+ uint16 :ticket_size, label: 'Ticket Size', initial_value: -> { num_bytes }
13
+ uint16 :user, label: 'User'
14
+ uint16 :user_name, label: 'User Name'
15
+ uint16 :domain, label: 'Domain'
16
+ uint16 :groups, label: 'Groups'
17
+ uint16 :restricted_groups, label: 'Restricted Groups'
18
+ uint16 :privileges, label: 'Privileges'
19
+ uint16 :primary_group, label: 'Primary Group'
20
+ uint16 :owner, label: 'Owner'
21
+ uint16 :default_dacl, label: 'Default DACL'
22
+ uint16 :device_groups, label: 'Device Groups'
23
+ uint16 :user_claims, label: 'User Claims'
24
+ uint16 :device_claims, label: 'Device Claims'
25
+ string :ticket_info, label: 'Ticket Info', read_length: -> { ticket_size - ticket_info.rel_offset}
26
+ end
27
+
28
+ # An SMB2 TreeConnectContext Packet as defined in
29
+ # [2.2.9.2 SMB2 TREE_CONNECT_CONTEXT Request Values](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/06eaaabc-caca-4776-9daf-82439e90dacd)
30
+ class TreeConnectContext < BinData::Record
31
+
32
+ # Context Types
33
+
34
+ # This value is reserved.
35
+ SMB2_RESERVED_TREE_CONNECT_CONTEXT_ID = 0x0000
36
+ # The Data field contains remoted identity tree connect context data as
37
+ # specified in section [2.2.9.2.1](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/ee7ff411-93e0-484f-9f73-31916fee4cb8)
38
+ SMB2_REMOTED_IDENTITY_TREE_CONNECT_CONTEXT_ID = 0x0001
39
+
40
+ endian :little
41
+ uint16 :context_type, label: 'Context Type'
42
+ uint16 :data_length, label: 'Data Length', initial_value: -> { data.to_binary_s.size }
43
+ uint32 :reserved, label: 'Reserved'
44
+ choice :data, label: 'Data', selection: -> { context_type } do
45
+ remoted_identity_tree_connect_context SMB2_REMOTED_IDENTITY_TREE_CONNECT_CONTEXT_ID, label: 'Remoted Identity Tree Connect Context'
46
+ end
47
+
48
+ end
49
+
50
+ # An SMB2 TreeConnectRequestExtension Packet as defined in
51
+ # [2.2.9.1 SMB2 TREE_CONNECT Request Extension](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/9ca7328b-b6ca-41a7-9773-0fa237261b76)
52
+ class TreeConnectRequestExtension < BinData::Record
53
+ endian :little
54
+ uint32 :tree_connect_context_offset, label: 'Tree Connect Context Offset', initial_value: -> { tree_connect_contexts.rel_offset }
55
+ uint16 :tree_connect_context_count, label: 'Tree Connect Context Count', initial_value: -> { tree_connect_contexts.size }
56
+ string :reserved, label: 'Reserved', length: 10
57
+ string16 :path, label: 'Path Buffer'
58
+ array :tree_connect_contexts, label: 'Tree Connect Contexts', type: :tree_connect_context, initial_length: -> { tree_connect_context_count }
59
+ end
60
+
4
61
  # An SMB2 TreeConnectRequest Packet as defined in
5
62
  # [2.2.9 SMB2 TREE_CONNECT Request](https://msdn.microsoft.com/en-us/library/cc246567.aspx)
6
63
  class TreeConnectRequest < RubySMB::GenericPacket
7
64
  COMMAND = RubySMB::SMB2::Commands::TREE_CONNECT
8
65
 
66
+ # Flags (SMB 3.1.1 only)
67
+
68
+ # The client has previously connected to the specified cluster share
69
+ # using the SMB dialect of the connection on which the request is received.
70
+ SMB2_TREE_CONNECT_FLAG_CLUSTER_RECONNECT = 0x0001
71
+ # The client can handle synchronous share redirects via a Share Redirect
72
+ # error context response as specified in section [2.2.2.2.2](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/f3073a8b-9f0f-47c0-91e5-ec3be9a49f37).
73
+ SMB2_TREE_CONNECT_FLAG_REDIRECT_TO_OWNER = 0x0002
74
+ # A tree connect request extension, as specified in section
75
+ # [2.2.9.1](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/9ca7328b-b6ca-41a7-9773-0fa237261b76),
76
+ # is present, starting at the Buffer field of this tree connect request.
77
+ SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT = 0x0003
78
+
9
79
  endian :little
10
80
  smb2_header :smb2_header
11
81
  uint16 :structure_size, label: 'Structure Size', initial_value: 9
82
+ # The flags field is only used by SMB 3.1.1, it must be 0 for other versions
12
83
  uint16 :flags, label: 'Flags', initial_value: 0x00
13
- uint16 :path_offset, label: 'Path Offset', initial_value: 0x48
14
- uint16 :path_length, label: 'Path Length', initial_value: -> { path.length }
15
- string :path, label: 'Path Buffer'
16
-
17
- def encode_path(path)
18
- self.path = path.encode('utf-16le')
84
+ # if SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT flag is set, #path_offset
85
+ # will have to be updated with the correct offset of the path name,
86
+ # which is located in the TreeConnect Context.
87
+ uint16 :path_offset, label: 'Path Offset', initial_value: -> do
88
+ if flags == SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT
89
+ tree_connect_request_extension.path.abs_offset
90
+ else
91
+ path.abs_offset
92
+ end
93
+ end
94
+ # if SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT flag is set, #path_length
95
+ # will have to be updated with the correct full share path name,
96
+ # which is located in the TreeConnect Context.
97
+ uint16 :path_length, label: 'Path Length', initial_value: -> do
98
+ if flags == SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT
99
+ tree_connect_request_extension.path.to_binary_s.length
100
+ else
101
+ path.to_binary_s.length
102
+ end
19
103
  end
104
+ string16 :path, label: 'Path Buffer', onlyif: -> { flags != SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT }
105
+ tree_connect_request_extension :tree_connect_request_extension, label: 'Tree Connect Request Extension', onlyif: -> { flags == SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT }
20
106
  end
21
107
  end
22
108
  end
@@ -6,6 +6,14 @@ module RubySMB
6
6
  class TreeConnectResponse < RubySMB::GenericPacket
7
7
  COMMAND = RubySMB::SMB2::Commands::TREE_CONNECT
8
8
 
9
+ # Share Types
10
+ # Physical disk share
11
+ SMB2_SHARE_TYPE_DISK = 0x01
12
+ # Named pipe share
13
+ SMB2_SHARE_TYPE_PIPE = 0x02
14
+ # Printer share
15
+ SMB2_SHARE_TYPE_PRINT = 0x03
16
+
9
17
  endian :little
10
18
  smb2_header :smb2_header
11
19
  uint16 :structure_size, label: 'Structure Size', initial_value: 16
@@ -20,32 +28,6 @@ module RubySMB
20
28
  smb2_header.flags.reply = 1
21
29
  end
22
30
 
23
- # Returns the ACCESS_MASK for the Maximal Share Access Rights. The packet
24
- # defaults this to a {RubySMB::SMB2::BitField::DirectoryAccessMask}. If it is anything other than
25
- # a directory that has been connected to, it will re-cast it as a {RubySMB::SMB2::BitField::FileAccessMask}
26
- #
27
- # @return [RubySMB::SMB2::BitField::DirectoryAccessMask] if a directory was connected to
28
- # @return [RubySMB::SMB2::BitField::FileAccessMask] if anything else was connected to
29
- # @raise [RubySMB::Error::InvalidBitField] if ACCESS_MASK bit field is not valid
30
- def access_rights
31
- if is_directory?
32
- maximal_access
33
- else
34
- mask = maximal_access.to_binary_s
35
- begin
36
- RubySMB::SMB2::BitField::FileAccessMask.read(mask)
37
- rescue IOError
38
- raise RubySMB::Error::InvalidBitField, 'Invalid ACCESS_MASK for the Maximal Share Access Rights'
39
- end
40
- end
41
- end
42
-
43
- # Checks if the remote Tree is a directory
44
- #
45
- # @return [Boolean]
46
- def is_directory?
47
- share_type == 0x01
48
- end
49
31
  end
50
32
  end
51
33
  end