ruby_smb 2.0.7 → 2.0.11

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 (53) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/verify.yml +57 -0
  4. data/README.md +0 -1
  5. data/examples/auth_capture.rb +71 -0
  6. data/lib/ruby_smb/client/negotiation.rb +11 -13
  7. data/lib/ruby_smb/client.rb +32 -27
  8. data/lib/ruby_smb/compression/lznt1.rb +164 -0
  9. data/lib/ruby_smb/compression.rb +7 -0
  10. data/lib/ruby_smb/dialect.rb +45 -0
  11. data/lib/ruby_smb/dispatcher/base.rb +1 -1
  12. data/lib/ruby_smb/dispatcher/socket.rb +1 -1
  13. data/lib/ruby_smb/gss/provider/authenticator.rb +42 -0
  14. data/lib/ruby_smb/gss/provider/ntlm.rb +303 -0
  15. data/lib/ruby_smb/gss/provider.rb +35 -0
  16. data/lib/ruby_smb/gss.rb +56 -63
  17. data/lib/ruby_smb/ntlm.rb +45 -0
  18. data/lib/ruby_smb/server/server_client/negotiation.rb +155 -0
  19. data/lib/ruby_smb/server/server_client/session_setup.rb +82 -0
  20. data/lib/ruby_smb/server/server_client.rb +163 -0
  21. data/lib/ruby_smb/server.rb +54 -0
  22. data/lib/ruby_smb/signing.rb +59 -0
  23. data/lib/ruby_smb/smb1/packet/negotiate_response.rb +11 -11
  24. data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +1 -1
  25. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
  26. data/lib/ruby_smb/smb1/tree.rb +1 -1
  27. data/lib/ruby_smb/smb2/negotiate_context.rb +18 -2
  28. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +4 -0
  29. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +9 -0
  30. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +0 -1
  31. data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -2
  32. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +1 -1
  33. data/lib/ruby_smb/smb2/tree.rb +1 -1
  34. data/lib/ruby_smb/smb2.rb +3 -1
  35. data/lib/ruby_smb/version.rb +1 -1
  36. data/lib/ruby_smb.rb +5 -3
  37. data/spec/lib/ruby_smb/client_spec.rb +24 -16
  38. data/spec/lib/ruby_smb/compression/lznt1_spec.rb +32 -0
  39. data/spec/lib/ruby_smb/gss/provider/ntlm/account_spec.rb +32 -0
  40. data/spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb +101 -0
  41. data/spec/lib/ruby_smb/gss/provider/ntlm/os_version_spec.rb +32 -0
  42. data/spec/lib/ruby_smb/gss/provider/ntlm_spec.rb +113 -0
  43. data/spec/lib/ruby_smb/server/server_client_spec.rb +156 -0
  44. data/spec/lib/ruby_smb/server_spec.rb +32 -0
  45. data/spec/lib/ruby_smb/smb1/tree_spec.rb +4 -4
  46. data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +2 -2
  47. data/spec/lib/ruby_smb/smb2/tree_spec.rb +5 -5
  48. data/spec/spec_helper.rb +1 -1
  49. data.tar.gz.sig +3 -1
  50. metadata +30 -4
  51. metadata.gz.sig +0 -0
  52. data/.travis.yml +0 -6
  53. data/lib/ruby_smb/client/signing.rb +0 -64
data/lib/ruby_smb/gss.rb CHANGED
@@ -2,6 +2,27 @@ module RubySMB
2
2
  # module containing methods required for using the [GSS-API](http://www.rfc-editor.org/rfc/rfc2743.txt)
3
3
  # for Secure Protected Negotiation(SPNEGO) in SMB Authentication.
4
4
  module Gss
5
+ require 'ruby_smb/gss/provider'
6
+
7
+ OID_SPNEGO = OpenSSL::ASN1::ObjectId.new('1.3.6.1.5.5.2')
8
+ OID_NEGOEX = OpenSSL::ASN1::ObjectId.new('1.3.6.1.4.1.311.2.2.30')
9
+ OID_NTLMSSP = OpenSSL::ASN1::ObjectId.new('1.3.6.1.4.1.311.2.2.10')
10
+
11
+ # Allow safe navigation of a decoded ASN.1 data structure. Similar to Ruby's
12
+ # builtin Hash#dig method but using the #value attribute of each ASN object.
13
+ #
14
+ # @param asn The ASN object to apply the traversal path on.
15
+ # @param [Array] path The path to traverse, each element is passed to the
16
+ # ASN object's #value's #[] operator.
17
+ def self.asn1dig(asn, *path)
18
+ path.each do |part|
19
+ return nil unless asn&.value
20
+ asn = asn.value[part]
21
+ end
22
+
23
+ asn
24
+ end
25
+
5
26
  # Cargo culted from Rex. Hacked Together ASN1 encoding that works for our GSS purposes
6
27
  # @todo Document these magic numbers
7
28
  def self.asn1encode(str = '')
@@ -25,78 +46,50 @@ module RubySMB
25
46
  end
26
47
 
27
48
  # Create a GSS Security Blob of an NTLM Type 1 Message.
28
- # This code has been cargo culted and needs to be researched
29
- # and refactored into something better later.
30
- # @todo Refactor this into non-magical code
31
49
  def self.gss_type1(type1)
32
- "\x60".force_encoding('binary') + asn1encode(
33
- "\x06".force_encoding('binary') + asn1encode(
34
- "\x2b\x06\x01\x05\x05\x02".force_encoding('binary')
35
- ) +
36
- "\xa0".force_encoding('binary') + asn1encode(
37
- "\x30".force_encoding('binary') + asn1encode(
38
- "\xa0".force_encoding('binary') + asn1encode(
39
- "\x30".force_encoding('binary') + asn1encode(
40
- "\x06".force_encoding('binary') + asn1encode(
41
- "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a".force_encoding('binary')
42
- )
43
- )
44
- ) +
45
- "\xa2".force_encoding('binary') + asn1encode(
46
- "\x04".force_encoding('binary') + asn1encode(
47
- type1
48
- )
49
- )
50
- )
51
- )
52
- )
50
+ OpenSSL::ASN1::ASN1Data.new([
51
+ OID_SPNEGO,
52
+ OpenSSL::ASN1::ASN1Data.new([
53
+ OpenSSL::ASN1::Sequence.new([
54
+ OpenSSL::ASN1::ASN1Data.new([
55
+ OpenSSL::ASN1::Sequence.new([
56
+ OID_NTLMSSP
57
+ ])
58
+ ], 0, :CONTEXT_SPECIFIC),
59
+ OpenSSL::ASN1::ASN1Data.new([
60
+ OpenSSL::ASN1::OctetString.new(type1)
61
+ ], 2, :CONTEXT_SPECIFIC)
62
+ ])
63
+ ], 0, :CONTEXT_SPECIFIC)
64
+ ], 0, :APPLICATION).to_der
53
65
  end
54
66
 
55
67
  # Create a GSS Security Blob of an NTLM Type 2 Message.
56
- # This code has been cargo culted and needs to be researched
57
- # and refactored into something better later.
58
68
  def self.gss_type2(type2)
59
- blob =
60
- "\xa1" + asn1encode(
61
- "\x30" + asn1encode(
62
- "\xa0" + asn1encode(
63
- "\x0a" + asn1encode(
64
- "\x01"
65
- )
66
- ) +
67
- "\xa1" + asn1encode(
68
- "\x06" + asn1encode(
69
- "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a"
70
- )
71
- ) +
72
- "\xa2" + asn1encode(
73
- "\x04" + asn1encode(
74
- type2
75
- )
76
- )
77
- )
78
- )
79
-
80
- blob
69
+ OpenSSL::ASN1::ASN1Data.new([
70
+ OpenSSL::ASN1::Sequence.new([
71
+ OpenSSL::ASN1::ASN1Data.new([
72
+ OpenSSL::ASN1::Enumerated.new(OpenSSL::BN.new(1))
73
+ ], 0, :CONTEXT_SPECIFIC),
74
+ OpenSSL::ASN1::ASN1Data.new([
75
+ OID_NTLMSSP
76
+ ], 1, :CONTEXT_SPECIFIC),
77
+ OpenSSL::ASN1::ASN1Data.new([
78
+ OpenSSL::ASN1::OctetString.new(type2)
79
+ ], 2, :CONTEXT_SPECIFIC)
80
+ ])
81
+ ], 1, :CONTEXT_SPECIFIC).to_der
81
82
  end
82
83
 
83
84
  # Create a GSS Security Blob of an NTLM Type 3 Message.
84
- # This code has been cargo culted and needs to be researched
85
- # and refactored into something better later.
86
- # @todo Refactor this into non-magical code
87
85
  def self.gss_type3(type3)
88
- gss =
89
- "\xa1".force_encoding('binary') + asn1encode(
90
- "\x30".force_encoding('binary') + asn1encode(
91
- "\xa2".force_encoding('binary') + asn1encode(
92
- "\x04".force_encoding('binary') + asn1encode(
93
- type3
94
- )
95
- )
96
- )
97
- )
98
-
99
- gss
86
+ OpenSSL::ASN1::ASN1Data.new([
87
+ OpenSSL::ASN1::Sequence.new([
88
+ OpenSSL::ASN1::ASN1Data.new([
89
+ OpenSSL::ASN1::OctetString.new(type3)
90
+ ], 2, :CONTEXT_SPECIFIC)
91
+ ])
92
+ ], 1, :CONTEXT_SPECIFIC).to_der
100
93
  end
101
94
  end
102
95
  end
@@ -0,0 +1,45 @@
1
+ module RubySMB
2
+ module NTLM
3
+ # [[MS-NLMP] 2.2.2.5](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/99d90ff4-957f-4c8a-80e4-5bfe5a9a9832)
4
+ NEGOTIATE_FLAGS = {
5
+ :UNICODE => 1 << 0,
6
+ :OEM => 1 << 1,
7
+ :REQUEST_TARGET => 1 << 2,
8
+ :SIGN => 1 << 4,
9
+ :SEAL => 1 << 5,
10
+ :DATAGRAM => 1 << 6,
11
+ :LAN_MANAGER_KEY => 1 << 7,
12
+ :NTLM => 1 << 9,
13
+ :NT_ONLY => 1 << 10,
14
+ :ANONYMOUS => 1 << 11,
15
+ :OEM_DOMAIN_SUPPLIED => 1 << 12,
16
+ :OEM_WORKSTATION_SUPPLIED => 1 << 13,
17
+ :ALWAYS_SIGN => 1 << 15,
18
+ :TARGET_TYPE_DOMAIN => 1 << 16,
19
+ :TARGET_TYPE_SERVER => 1 << 17,
20
+ :TARGET_TYPE_SHARE => 1 << 18,
21
+ :EXTENDED_SECURITY => 1 << 19,
22
+ :IDENTIFY => 1 << 20,
23
+ :NON_NT_SESSION => 1 << 22,
24
+ :TARGET_INFO => 1 << 23,
25
+ :VERSION_INFO => 1 << 25,
26
+ :KEY128 => 1 << 29,
27
+ :KEY_EXCHANGE => 1 << 30,
28
+ :KEY56 => 1 << 31
29
+ }.freeze
30
+
31
+ class OSVersion < BinData::Record
32
+ endian :big
33
+
34
+ uint8 :major
35
+ uint8 :minor
36
+ uint16 :build
37
+ uint32 :ntlm_revision, initial_value: 15
38
+
39
+ def to_s
40
+ "Version #{major}.#{minor} (Build #{build}); NTLM Current Revision #{ntlm_revision}"
41
+ end
42
+ end
43
+ end
44
+ end
45
+
@@ -0,0 +1,155 @@
1
+ require 'securerandom'
2
+
3
+ module RubySMB
4
+ class Server
5
+ class ServerClient
6
+ module Negotiation
7
+ #
8
+ # Handle an SMB negotiation request. Once negotiation is complete, the state will be updated to :session_setup.
9
+ # At this point the @dialect will have been set along with other dialect-specific values.
10
+ #
11
+ # @param [String] raw_request the negotiation request to process
12
+ def handle_negotiate(raw_request)
13
+ response = nil
14
+ case raw_request[0...4].unpack1('L>')
15
+ when RubySMB::SMB1::SMB_PROTOCOL_ID
16
+ request = SMB1::Packet::NegotiateRequest.read(raw_request)
17
+ response = do_negotiate_smb1(request) if request.is_a?(SMB1::Packet::NegotiateRequest)
18
+ when RubySMB::SMB2::SMB2_PROTOCOL_ID
19
+ request = SMB2::Packet::NegotiateRequest.read(raw_request)
20
+ response = do_negotiate_smb2(request) if request.is_a?(SMB2::Packet::NegotiateRequest)
21
+ end
22
+
23
+ if response.nil?
24
+ disconnect!
25
+ else
26
+ send_packet(response)
27
+ end
28
+
29
+ nil
30
+ end
31
+
32
+ def do_negotiate_smb1(request)
33
+ client_dialects = request.dialects.map(&:dialect_string).map(&:value)
34
+
35
+ if client_dialects.include?(Client::SMB1_DIALECT_SMB2_WILDCARD) && \
36
+ @server.dialects.any? { |dialect| Dialect[dialect].order == Dialect::ORDER_SMB2 }
37
+ response = SMB2::Packet::NegotiateResponse.new
38
+ response.smb2_header.credits = 1
39
+ response.security_mode.signing_enabled = 1
40
+ response.dialect_revision = SMB2::SMB2_WILDCARD_REVISION
41
+ response.server_guid = @server.guid
42
+
43
+ response.max_transact_size = 0x800000
44
+ response.max_read_size = 0x800000
45
+ response.max_write_size = 0x800000
46
+ response.system_time.set(Time.now)
47
+ response.security_buffer_offset = response.security_buffer.abs_offset
48
+ response.security_buffer = process_gss.buffer
49
+ return response
50
+ end
51
+
52
+ server_dialects = @server.dialects.select { |dialect| Dialect[dialect].order == Dialect::ORDER_SMB1 }
53
+ dialect = (server_dialects & client_dialects).first
54
+ if dialect.nil?
55
+ # 'NT LM 0.12' is currently the only supported dialect
56
+ # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/80850595-e301-4464-9745-58e4945eb99b
57
+ response = SMB1::Packet::NegotiateResponse.new
58
+ response.parameter_block.word_count = 1
59
+ response.parameter_block.dialect_index = 0xffff
60
+ response.data_block.byte_count = 0
61
+ return response
62
+ end
63
+
64
+ response = SMB1::Packet::NegotiateResponseExtended.new
65
+ response.parameter_block.dialect_index = client_dialects.index(dialect)
66
+ response.parameter_block.max_mpx_count = 50
67
+ response.parameter_block.max_number_vcs = 1
68
+ response.parameter_block.max_buffer_size = 16644
69
+ response.parameter_block.max_raw_size = 65536
70
+ server_time = Time.now
71
+ response.parameter_block.system_time.set(server_time)
72
+ response.parameter_block.server_time_zone = server_time.utc_offset
73
+ response.data_block.server_guid = @server.guid
74
+ response.data_block.security_blob = process_gss.buffer
75
+
76
+ @state = :session_setup
77
+ @dialect = dialect
78
+ response
79
+ end
80
+
81
+ def do_negotiate_smb2(request)
82
+ client_dialects = request.dialects.map { |d| "0x%04x" % d }
83
+ server_dialects = @server.dialects.select { |dialect| Dialect[dialect].order == Dialect::ORDER_SMB2 }
84
+ dialect = (server_dialects & client_dialects).first
85
+
86
+ response = SMB2::Packet::NegotiateResponse.new
87
+ response.smb2_header.credits = 1
88
+ response.security_mode.signing_enabled = 1
89
+ response.server_guid = @server.guid
90
+ response.max_transact_size = 0x800000
91
+ response.max_read_size = 0x800000
92
+ response.max_write_size = 0x800000
93
+ response.system_time.set(Time.now)
94
+ if dialect.nil?
95
+ # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/b39f253e-4963-40df-8dff-2f9040ebbeb1
96
+ # > If a common dialect is not found, the server MUST fail the request with STATUS_NOT_SUPPORTED.
97
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED.value
98
+ return response
99
+ end
100
+
101
+ contexts = []
102
+ hash_algorithm = hash_value = nil
103
+ if dialect == '0x0311'
104
+ # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/b39f253e-4963-40df-8dff-2f9040ebbeb1
105
+ nc = request.find_negotiate_context(SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES)
106
+ hash_algorithm = SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[nc&.data&.hash_algorithms&.first]
107
+ hash_value = "\x00" * 64
108
+ unless hash_algorithm
109
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER.value
110
+ return response
111
+ end
112
+
113
+ contexts << SMB2::NegotiateContext.new(
114
+ context_type: SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES,
115
+ data: {
116
+ hash_algorithms: [ SMB2::PreauthIntegrityCapabilities::SHA_512 ],
117
+ salt: SecureRandom.random_bytes(32)
118
+ }
119
+ )
120
+
121
+ nc = request.find_negotiate_context(SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES)
122
+ cipher = nc&.data&.ciphers&.first
123
+ cipher = 0 unless SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP.include? cipher
124
+ contexts << SMB2::NegotiateContext.new(
125
+ context_type: SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES,
126
+ data: {
127
+ ciphers: [ cipher ]
128
+ }
129
+ )
130
+ end
131
+
132
+ # the order in which the response is built is important to ensure it is valid
133
+ response.dialect_revision = dialect.to_i(16)
134
+ response.security_buffer_offset = response.security_buffer.abs_offset
135
+ response.security_buffer = process_gss.buffer
136
+ if dialect == '0x0311'
137
+ response.negotiate_context_offset = response.negotiate_context_list.abs_offset
138
+ contexts.each { |nc| response.add_negotiate_context(nc) }
139
+ end
140
+ @preauth_integrity_hash_algorithm = hash_algorithm
141
+ @preauth_integrity_hash_value = hash_value
142
+
143
+ if dialect == '0x0311'
144
+ update_preauth_hash(request)
145
+ update_preauth_hash(response)
146
+ end
147
+
148
+ @state = :session_setup
149
+ @dialect = dialect
150
+ response
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,82 @@
1
+ module RubySMB
2
+ class Server
3
+ class ServerClient
4
+ module SessionSetup
5
+ #
6
+ # Setup a new session based on the negotiated dialect. Once session setup is complete, the state will be updated
7
+ # to :authenticated.
8
+ #
9
+ # @param [String] raw_request the session setup request to process
10
+ def handle_session_setup(raw_request)
11
+ response = nil
12
+
13
+ case metadialect.order
14
+ when Dialect::ORDER_SMB1
15
+ request = SMB1::Packet::SessionSetupRequest.read(raw_request)
16
+ response = do_session_setup_smb1(request)
17
+ when Dialect::ORDER_SMB2
18
+ request = SMB2::Packet::SessionSetupRequest.read(raw_request)
19
+ response = do_session_setup_smb2(request)
20
+ end
21
+
22
+ if response.nil?
23
+ disconnect!
24
+ else
25
+ send_packet(response)
26
+ end
27
+
28
+ nil
29
+ end
30
+
31
+ def do_session_setup_smb1(request)
32
+ gss_result = process_gss(request.data_block.security_blob)
33
+ return if gss_result.nil?
34
+
35
+ response = SMB1::Packet::SessionSetupResponse.new
36
+ response.smb_header.pid_low = request.smb_header.pid_low
37
+ response.smb_header.uid = rand(0x10000)
38
+ response.smb_header.mid = request.smb_header.mid
39
+ response.smb_header.nt_status = gss_result.nt_status.value
40
+ response.smb_header.flags.reply = true
41
+ response.smb_header.flags2.unicode = true
42
+ response.smb_header.flags2.extended_security = true
43
+ unless gss_result.buffer.nil?
44
+ response.parameter_block.security_blob_length = gss_result.buffer.length
45
+ response.data_block.security_blob = gss_result.buffer
46
+ end
47
+
48
+ if gss_result.nt_status == WindowsError::NTStatus::STATUS_SUCCESS
49
+ @state = :authenticated
50
+ @identity = gss_result.identity
51
+ end
52
+
53
+ response
54
+ end
55
+
56
+ def do_session_setup_smb2(request)
57
+ gss_result = process_gss(request.buffer)
58
+ return if gss_result.nil?
59
+
60
+ response = SMB2::Packet::SessionSetupResponse.new
61
+ response.smb2_header.nt_status = gss_result.nt_status.value
62
+ response.smb2_header.credits = 1
63
+ response.smb2_header.message_id = @message_id += 1
64
+ response.smb2_header.session_id = @session_id = @session_id || SecureRandom.random_bytes(4).unpack1('V')
65
+ response.buffer = gss_result.buffer
66
+
67
+ update_preauth_hash(request) if @dialect == '0x0311'
68
+ if gss_result.nt_status == WindowsError::NTStatus::STATUS_SUCCESS
69
+ @state = :authenticated
70
+ @identity = gss_result.identity
71
+ @session_key = @gss_authenticator.session_key
72
+ elsif gss_result.nt_status == WindowsError::NTStatus::STATUS_MORE_PROCESSING_REQUIRED && @dialect == '0x0311'
73
+ update_preauth_hash(response)
74
+ end
75
+
76
+ response
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+
@@ -0,0 +1,163 @@
1
+ module RubySMB
2
+ class Server
3
+ # This class represents a single connected client to the server. It stores and processes connection specific related
4
+ # information.
5
+ class ServerClient
6
+
7
+ require 'ruby_smb/dialect'
8
+ require 'ruby_smb/signing'
9
+ require 'ruby_smb/server/server_client/negotiation'
10
+ require 'ruby_smb/server/server_client/session_setup'
11
+
12
+ include RubySMB::Signing
13
+ include RubySMB::Server::ServerClient::Negotiation
14
+ include RubySMB::Server::ServerClient::SessionSetup
15
+
16
+ attr_reader :dialect, :identity, :state, :session_key
17
+
18
+ # @param [Server] server the server that accepted this connection
19
+ # @param [Dispatcher::Socket] dispatcher the connection's socket dispatcher
20
+ def initialize(server, dispatcher)
21
+ @server = server
22
+ @dispatcher = dispatcher
23
+ @state = :negotiate
24
+ @dialect = nil
25
+ @message_id = 0
26
+ @session_id = nil
27
+ @session_key = nil
28
+ @gss_authenticator = server.gss_provider.new_authenticator(self)
29
+ @identity = nil
30
+ @tree_connections = {}
31
+ @preauth_integrity_hash_algorithm = nil
32
+ @preauth_integrity_hash_value = nil
33
+ end
34
+
35
+ #
36
+ # The dialects metadata definition.
37
+ #
38
+ # @return [Dialect::Definition]
39
+ def metadialect
40
+ Dialect::ALL[@dialect]
41
+ end
42
+
43
+ #
44
+ # The peername of the connected socket. This is a combination of the IPv4 or IPv6 address and port number.
45
+ #
46
+ # @example Parse the value into an IP address
47
+ # ::Socket::unpack_sockaddr_in(server_client.getpeername)
48
+ #
49
+ # @return [String]
50
+ def getpeername
51
+ @dispatcher.tcp_socket.getpeername
52
+ end
53
+
54
+ #
55
+ # Handle an authenticated request. This is the main handler for all requests after the connection has been
56
+ # authenticated.
57
+ #
58
+ # @param [String] raw_request the request that should be handled
59
+ def handle_authenticated(raw_request)
60
+ response = nil
61
+
62
+ case raw_request[0...4].unpack1('L>')
63
+ when RubySMB::SMB1::SMB_PROTOCOL_ID
64
+ raise NotImplementedError
65
+ when RubySMB::SMB2::SMB2_PROTOCOL_ID
66
+ raise NotImplementedError
67
+ end
68
+
69
+ if response.nil?
70
+ disconnect!
71
+ return
72
+ end
73
+
74
+ send_packet(response)
75
+ end
76
+
77
+ #
78
+ # Process a GSS authentication buffer. If no buffer is specified, the request is assumed to be the first in the
79
+ # negotiation sequence.
80
+ #
81
+ # @param [String, nil] buffer the request GSS request buffer that should be processed
82
+ # @return [Gss::Provider::Result] the result of the processed GSS request
83
+ def process_gss(buffer=nil)
84
+ @gss_authenticator.process(buffer)
85
+ end
86
+
87
+ #
88
+ # Run the processing loop to receive and handle requests. This loop runs until an exception occurs or the
89
+ # dispatcher socket is closed.
90
+ #
91
+ def run
92
+ loop do
93
+ begin
94
+ raw_request = recv_packet
95
+ rescue RubySMB::Error::CommunicationError
96
+ break
97
+ end
98
+
99
+ case @state
100
+ when :negotiate
101
+ handle_negotiate(raw_request)
102
+ when :session_setup
103
+ handle_session_setup(raw_request)
104
+ when :authenticated
105
+ handle_authenticated(raw_request)
106
+ end
107
+
108
+ break if @dispatcher.tcp_socket.closed?
109
+ end
110
+ end
111
+
112
+ #
113
+ # Disconnect the remote client.
114
+ #
115
+ def disconnect!
116
+ @state = nil
117
+ @dispatcher.tcp_socket.close
118
+ end
119
+
120
+ #
121
+ # Receive a single SMB packet from the dispatcher.
122
+ #
123
+ # @return [String] the raw packet
124
+ def recv_packet
125
+ @dispatcher.recv_packet
126
+ end
127
+
128
+ #
129
+ # Send a single SMB packet using the dispatcher. If necessary, the packet will be signed.
130
+ #
131
+ # @param [GenericPacket] packet the packet to send
132
+ def send_packet(packet)
133
+ if @state == :authenticated && @identity != Gss::Provider::IDENTITY_ANONYMOUS && !@session_key.nil?
134
+ case metadialect.family
135
+ when Dialect::FAMILY_SMB2
136
+ packet = smb2_sign(packet)
137
+ when Dialect::FAMILY_SMB3
138
+ packet = smb3_sign(packet)
139
+ end
140
+ end
141
+
142
+ @dispatcher.send_packet(packet)
143
+ end
144
+
145
+ #
146
+ # Update the preauth integrity hash as used by dialect 3.1.1 for various cryptographic operations. The algorithm
147
+ # and hash values must have been initialized prior to calling this.
148
+ #
149
+ # @param [String] data the data with which to update the preauth integrity hash
150
+ def update_preauth_hash(data)
151
+ unless @preauth_integrity_hash_algorithm
152
+ raise RubySMB::Error::EncryptionError.new(
153
+ 'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
154
+ )
155
+ end
156
+ @preauth_integrity_hash_value = OpenSSL::Digest.digest(
157
+ @preauth_integrity_hash_algorithm,
158
+ @preauth_integrity_hash_value + data.to_binary_s
159
+ )
160
+ end
161
+ end
162
+ end
163
+ end