ruby_smb 2.0.7 → 2.0.11

Sign up to get free protection for your applications and to get access to all the features.
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