ruby_smb 3.0.6 → 3.1.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/ruby_smb/client/encryption.rb +16 -4
- data/lib/ruby_smb/client/negotiation.rb +3 -1
- data/lib/ruby_smb/fscc/file_information.rb +4 -0
- data/lib/ruby_smb/server/server_client/encryption.rb +66 -0
- data/lib/ruby_smb/server/server_client/negotiation.rb +14 -3
- data/lib/ruby_smb/server/server_client/session_setup.rb +18 -3
- data/lib/ruby_smb/server/server_client/share_io.rb +17 -0
- data/lib/ruby_smb/server/server_client/tree_connect.rb +40 -3
- data/lib/ruby_smb/server/server_client.rb +147 -37
- data/lib/ruby_smb/server/share/provider/disk/file_system.rb +28 -0
- data/lib/ruby_smb/server/share/provider/disk/processor/close.rb +42 -0
- data/lib/ruby_smb/server/share/provider/disk/processor/create.rb +143 -0
- data/lib/ruby_smb/server/share/provider/disk/processor/query.rb +359 -0
- data/lib/ruby_smb/server/share/provider/disk/processor/read.rb +69 -0
- data/lib/ruby_smb/server/share/provider/disk/processor.rb +159 -0
- data/lib/ruby_smb/server/share/provider/disk.rb +4 -416
- data/lib/ruby_smb/server/share/provider/pipe.rb +2 -2
- data/lib/ruby_smb/server/share/provider/processor.rb +16 -0
- data/lib/ruby_smb/signing.rb +18 -4
- data/lib/ruby_smb/smb1/commands.rb +1 -0
- data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +11 -1
- data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/read_andx_response.rb +5 -4
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +12 -4
- data/lib/ruby_smb/smb1/packet/trans2/data_block.rb +9 -1
- data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +52 -51
- data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +37 -37
- data/lib/ruby_smb/smb1/packet/trans2/find_information_level/find_file_both_directory_info.rb +48 -0
- data/lib/ruby_smb/smb1/packet/trans2/find_information_level.rb +28 -15
- data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +51 -51
- data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +36 -36
- data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +40 -39
- data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +40 -40
- data/lib/ruby_smb/smb1/packet/trans2/query_file_information_request.rb +60 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_file_information_response.rb +59 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level/query_fs_attribute_info.rb +31 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level.rb +40 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request.rb +46 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response.rb +59 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_basic_info.rb +23 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_standard_info.rb +22 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_information_level.rb +62 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_path_information_request.rb +65 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_path_information_response.rb +59 -0
- data/lib/ruby_smb/smb1/packet/trans2/request.rb +24 -8
- data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +4 -4
- data/lib/ruby_smb/smb1/packet/trans2/response.rb +29 -20
- data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +42 -42
- data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +23 -23
- data/lib/ruby_smb/smb1/packet/trans2/subcommands.rb +23 -5
- data/lib/ruby_smb/smb1/packet/trans2.rb +4 -0
- data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +4 -1
- data/lib/ruby_smb/smb2/negotiate_context.rb +10 -1
- data/lib/ruby_smb/smb2/packet/transform_header.rb +7 -7
- data/lib/ruby_smb/smb2.rb +1 -0
- data/lib/ruby_smb/version.rb +1 -1
- data/spec/lib/ruby_smb/client_spec.rb +20 -6
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +36 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +35 -1
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_request_spec.rb +74 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_response_spec.rb +96 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request_spec.rb +62 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response_spec.rb +88 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_request_spec.rb +79 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_response_spec.rb +96 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/response_spec.rb +3 -3
- data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_request_spec.rb +3 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb +7 -2
- data/spec/lib/ruby_smb/smb1/tree_spec.rb +3 -3
- data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +33 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 582c72fe6559ee4c40aa9e33f77c6644669848ae3452c11095f31fb0b0fce387
|
4
|
+
data.tar.gz: 2791bfa79cb55e6ec6689782a747860287278bc42c38d6ecd03a66396efba200
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae54e45927264996fbaed098e38f38d179c0750aa4d82073910364bdc3cb2719c8ec89c9f432554ef44a752b9aef3e4f945b73357b4694c7faa52f7025582efc
|
7
|
+
data.tar.gz: 0cf5036c07e48a2cb6a40bdb63e0ec18f0f20d7110af4938f3fc4067e77d99e084ac5cba0f255980b1b971f93985793cce8211f4496e9ffd6be0bc00ab5e2dd7
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
@@ -4,18 +4,24 @@ module RubySMB
|
|
4
4
|
module Encryption
|
5
5
|
def smb3_encrypt(data)
|
6
6
|
unless @client_encryption_key
|
7
|
+
raise RubySMB::Error::EncryptionError.new('The encryption algorithm has not been set') if @encryption_algorithm.nil?
|
8
|
+
|
9
|
+
key_bit_len = OpenSSL::Cipher.new(@encryption_algorithm).key_len * 8
|
10
|
+
|
7
11
|
case @dialect
|
8
12
|
when '0x0300', '0x0302'
|
9
13
|
@client_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
10
14
|
@session_key,
|
11
15
|
"SMB2AESCCM\x00",
|
12
|
-
"ServerIn \x00"
|
16
|
+
"ServerIn \x00",
|
17
|
+
length: key_bit_len
|
13
18
|
)
|
14
19
|
when '0x0311'
|
15
20
|
@client_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
16
21
|
@session_key,
|
17
22
|
"SMBC2SCipherKey\x00",
|
18
|
-
@preauth_integrity_hash_value
|
23
|
+
@preauth_integrity_hash_value,
|
24
|
+
length: key_bit_len
|
19
25
|
)
|
20
26
|
else
|
21
27
|
raise RubySMB::Error::EncryptionError.new('Dialect is incompatible with SMBv3 encryption')
|
@@ -33,18 +39,24 @@ module RubySMB
|
|
33
39
|
|
34
40
|
def smb3_decrypt(th)
|
35
41
|
unless @server_encryption_key
|
42
|
+
raise RubySMB::Error::EncryptionError.new('The encryption algorithm has not been set') if @encryption_algorithm.nil?
|
43
|
+
|
44
|
+
key_bit_len = OpenSSL::Cipher.new(@encryption_algorithm).key_len * 8
|
45
|
+
|
36
46
|
case @dialect
|
37
47
|
when '0x0300', '0x0302'
|
38
48
|
@server_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
39
49
|
@session_key,
|
40
50
|
"SMB2AESCCM\x00",
|
41
|
-
"ServerOut\x00"
|
51
|
+
"ServerOut\x00",
|
52
|
+
length: key_bit_len
|
42
53
|
)
|
43
54
|
when '0x0311'
|
44
55
|
@server_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
45
56
|
@session_key,
|
46
57
|
"SMBS2CCipherKey\x00",
|
47
|
-
@preauth_integrity_hash_value
|
58
|
+
@preauth_integrity_hash_value,
|
59
|
+
length: key_bit_len
|
48
60
|
)
|
49
61
|
else
|
50
62
|
raise RubySMB::Error::EncryptionError.new('Dialect is incompatible with SMBv3 decryption')
|
@@ -265,8 +265,10 @@ module RubySMB
|
|
265
265
|
nc = RubySMB::SMB2::NegotiateContext.new(
|
266
266
|
context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
267
267
|
)
|
268
|
-
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::
|
268
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_256_GCM
|
269
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_256_CCM
|
269
270
|
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
271
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
|
270
272
|
packet.add_negotiate_context(nc)
|
271
273
|
|
272
274
|
nc = RubySMB::SMB2::NegotiateContext.new(
|
@@ -65,6 +65,10 @@ module RubySMB
|
|
65
65
|
# [2.2.2.3.5 Pass-through Information Level Codes](https://msdn.microsoft.com/en-us/library/ff470158.aspx)
|
66
66
|
SMB_INFO_PASSTHROUGH = 0x03e8
|
67
67
|
|
68
|
+
def self.name(value)
|
69
|
+
constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value }
|
70
|
+
end
|
71
|
+
|
68
72
|
# The FILE_NAME_INFORMATION type as defined in
|
69
73
|
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/20406fb1-605f-4629-ba9a-c67ee25f23d2
|
70
74
|
class FileNameInformation < BinData::Record
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module RubySMB
|
2
|
+
class Server
|
3
|
+
class ServerClient
|
4
|
+
# Contains the methods for handling encryption / decryption
|
5
|
+
module Encryption
|
6
|
+
def smb3_encrypt(data, session)
|
7
|
+
encryption_algorithm = SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[@cipher_id]
|
8
|
+
raise RubySMB::Error::EncryptionError.new('The encryption algorithm has not been set') if encryption_algorithm.nil?
|
9
|
+
|
10
|
+
key_bit_len = OpenSSL::Cipher.new(encryption_algorithm).key_len * 8
|
11
|
+
|
12
|
+
case @dialect
|
13
|
+
when '0x0300', '0x0302'
|
14
|
+
server_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
15
|
+
session.key,
|
16
|
+
"SMB2AESCCM\x00",
|
17
|
+
"ServerOut\x00",
|
18
|
+
length: key_bit_len
|
19
|
+
)
|
20
|
+
when '0x0311'
|
21
|
+
server_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
22
|
+
session.key,
|
23
|
+
"SMBS2CCipherKey\x00",
|
24
|
+
@preauth_integrity_hash_value,
|
25
|
+
length: key_bit_len
|
26
|
+
)
|
27
|
+
else
|
28
|
+
raise RubySMB::Error::EncryptionError.new('Dialect is incompatible with SMBv3 decryption')
|
29
|
+
end
|
30
|
+
|
31
|
+
th = RubySMB::SMB2::Packet::TransformHeader.new(flags: 1, session_id: session.id)
|
32
|
+
th.encrypt(data, server_encryption_key, algorithm: encryption_algorithm)
|
33
|
+
th
|
34
|
+
end
|
35
|
+
|
36
|
+
def smb3_decrypt(encrypted_request, session)
|
37
|
+
encryption_algorithm = SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[@cipher_id]
|
38
|
+
raise RubySMB::Error::EncryptionError.new('The encryption algorithm has not been set') if encryption_algorithm.nil?
|
39
|
+
|
40
|
+
key_bit_len = OpenSSL::Cipher.new(encryption_algorithm).key_len * 8
|
41
|
+
|
42
|
+
case @dialect
|
43
|
+
when '0x0300', '0x0302'
|
44
|
+
client_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
45
|
+
session.key,
|
46
|
+
"SMB2AESCCM\x00",
|
47
|
+
"ServerIn \x00",
|
48
|
+
length: key_bit_len
|
49
|
+
)
|
50
|
+
when '0x0311'
|
51
|
+
client_encryption_key = RubySMB::Crypto::KDF.counter_mode(
|
52
|
+
session.key,
|
53
|
+
"SMBC2SCipherKey\x00",
|
54
|
+
@preauth_integrity_hash_value,
|
55
|
+
length: key_bit_len
|
56
|
+
)
|
57
|
+
else
|
58
|
+
raise RubySMB::Error::EncryptionError.new('Dialect is incompatible with SMBv3 encryption')
|
59
|
+
end
|
60
|
+
|
61
|
+
encrypted_request.decrypt(client_encryption_key, algorithm: encryption_algorithm)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -119,14 +119,25 @@ module RubySMB
|
|
119
119
|
)
|
120
120
|
|
121
121
|
nc = request.find_negotiate_context(SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES)
|
122
|
-
|
123
|
-
|
122
|
+
ciphers = nc&.data&.ciphers
|
123
|
+
if ciphers
|
124
|
+
cipher = ciphers.find { |cipher| SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP.include?(cipher) }
|
125
|
+
@cipher_id = cipher unless cipher.nil?
|
126
|
+
end
|
127
|
+
|
124
128
|
contexts << SMB2::NegotiateContext.new(
|
125
129
|
context_type: SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES,
|
126
130
|
data: {
|
127
|
-
ciphers: [
|
131
|
+
ciphers: [ @cipher_id ]
|
128
132
|
}
|
129
133
|
)
|
134
|
+
elsif dialect == '0x0300' || dialect == '0x0302'
|
135
|
+
if request.capabilities.encryption == 1
|
136
|
+
response.capabilities.encryption = 1
|
137
|
+
@cipher_id = SMB2::EncryptionCapabilities::AES_128_CCM
|
138
|
+
else
|
139
|
+
response.capabilities = 0
|
140
|
+
end
|
130
141
|
end
|
131
142
|
|
132
143
|
# the order in which the response is built is important to ensure it is valid
|
@@ -2,7 +2,7 @@ module RubySMB
|
|
2
2
|
class Server
|
3
3
|
class ServerClient
|
4
4
|
module SessionSetup
|
5
|
-
def
|
5
|
+
def do_session_setup_andx_smb1(request, session)
|
6
6
|
session_id = request.smb_header.uid
|
7
7
|
if session_id == 0
|
8
8
|
session_id = rand(1..0x10000)
|
@@ -41,6 +41,17 @@ module RubySMB
|
|
41
41
|
response
|
42
42
|
end
|
43
43
|
|
44
|
+
alias :do_session_setup_smb1 :do_session_setup_andx_smb1
|
45
|
+
|
46
|
+
def do_logoff_andx_smb1(request, session)
|
47
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/00fc0299-496c-4330-9089-67358994f272
|
48
|
+
@session_table.delete(request.smb_header.uid)
|
49
|
+
session.logoff!
|
50
|
+
|
51
|
+
response = SMB1::Packet::LogoffResponse.new
|
52
|
+
response
|
53
|
+
end
|
54
|
+
|
44
55
|
def do_session_setup_smb2(request, session)
|
45
56
|
session_id = request.smb2_header.session_id
|
46
57
|
if session_id == 0
|
@@ -67,11 +78,14 @@ module RubySMB
|
|
67
78
|
|
68
79
|
update_preauth_hash(request) if @dialect == '0x0311'
|
69
80
|
if gss_result.nt_status == WindowsError::NTStatus::STATUS_SUCCESS
|
70
|
-
response.smb2_header.credits = 32
|
71
81
|
session.state = :valid
|
72
82
|
session.user_id = gss_result.identity
|
73
83
|
session.key = @gss_authenticator.session_key
|
74
84
|
session.signing_required = request.security_mode.signing_required == 1
|
85
|
+
|
86
|
+
response.smb2_header.credits = 32
|
87
|
+
@cipher_id = 0 if session.is_anonymous # disable encryption for anonymous users
|
88
|
+
response.session_flags.encrypt_data = 1 unless @cipher_id == 0
|
75
89
|
elsif gss_result.nt_status == WindowsError::NTStatus::STATUS_MORE_PROCESSING_REQUIRED && @dialect == '0x0311'
|
76
90
|
update_preauth_hash(response)
|
77
91
|
end
|
@@ -80,7 +94,8 @@ module RubySMB
|
|
80
94
|
end
|
81
95
|
|
82
96
|
def do_logoff_smb2(request, session)
|
83
|
-
|
97
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/a6fbc502-75a5-42ef-a88c-c67b44817850
|
98
|
+
@session_table.delete(session.id)
|
84
99
|
session.logoff!
|
85
100
|
|
86
101
|
response = SMB2::Packet::LogoffResponse.new
|
@@ -2,6 +2,23 @@ module RubySMB
|
|
2
2
|
class Server
|
3
3
|
class ServerClient
|
4
4
|
module ShareIO
|
5
|
+
def proxy_share_io_smb1(request, session)
|
6
|
+
share_processor = session.tree_connect_table[request.smb_header.tid]
|
7
|
+
if share_processor.nil?
|
8
|
+
response = SMB1::Packet::EmptyPacket.new
|
9
|
+
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_NETWORK_NAME_DELETED
|
10
|
+
return response
|
11
|
+
end
|
12
|
+
|
13
|
+
logger.debug("Received #{SMB1::Commands.name(request.smb_header.command)} request for share: #{share_processor.provider.name}")
|
14
|
+
share_processor.send(__callee__, request)
|
15
|
+
end
|
16
|
+
|
17
|
+
alias :do_close_smb1 :proxy_share_io_smb1
|
18
|
+
alias :do_nt_create_andx_smb1 :proxy_share_io_smb1
|
19
|
+
alias :do_read_andx_smb1 :proxy_share_io_smb1
|
20
|
+
alias :do_transactions2_smb1 :proxy_share_io_smb1
|
21
|
+
|
5
22
|
def proxy_share_io_smb2(request, session)
|
6
23
|
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/9a639360-87be-4d49-a1dd-4c6be0c020bd
|
7
24
|
share_processor = session.tree_connect_table[request.smb2_header.tree_id]
|
@@ -3,6 +3,43 @@ module RubySMB
|
|
3
3
|
class ServerClient
|
4
4
|
MAX_TREE_CONNECTIONS = 1000
|
5
5
|
module TreeConnect
|
6
|
+
def do_tree_connect_smb1(request, session)
|
7
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/b062f3e3-1b65-4a9a-854a-0ee432499d8f
|
8
|
+
response = RubySMB::SMB1::Packet::TreeConnectResponse.new
|
9
|
+
|
10
|
+
share_name = request.data_block.path.encode('UTF-8').split('\\', 4).last
|
11
|
+
share_provider = @server.shares.transform_keys(&:downcase)[share_name.downcase]
|
12
|
+
if share_provider.nil?
|
13
|
+
logger.warn("Received TREE_CONNECT request for non-existent share: #{share_name}")
|
14
|
+
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_OBJECT_PATH_NOT_FOUND
|
15
|
+
return response
|
16
|
+
end
|
17
|
+
logger.debug("Received TREE_CONNECT request for share: #{share_name}")
|
18
|
+
|
19
|
+
tree_id = rand(1..0xfffe)
|
20
|
+
tree_id = rand(1..0xfffe) while session.tree_connect_table.include?(tree_id)
|
21
|
+
|
22
|
+
response.smb_header.tid = tree_id
|
23
|
+
session.tree_connect_table[tree_id] = share_processor = share_provider.new_processor(self, session)
|
24
|
+
response.parameter_block.access_rights = share_processor.maximal_access
|
25
|
+
|
26
|
+
response
|
27
|
+
end
|
28
|
+
|
29
|
+
def do_tree_disconnect_smb1(request, session)
|
30
|
+
share_processor = session.tree_connect_table.delete(request.smb_header.tid)
|
31
|
+
if share_processor.nil?
|
32
|
+
response = RubySMB::SMB1::Packet::EmptyPacket.new
|
33
|
+
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_NETWORK_NAME_DELETED
|
34
|
+
return response
|
35
|
+
end
|
36
|
+
|
37
|
+
logger.debug("Received TREE_DISCONNECT request for share: #{share_processor.provider.name}")
|
38
|
+
share_processor.disconnect!
|
39
|
+
response = RubySMB::SMB1::Packet::TreeDisconnectResponse.new
|
40
|
+
response
|
41
|
+
end
|
42
|
+
|
6
43
|
def do_tree_connect_smb2(request, session)
|
7
44
|
response = RubySMB::SMB2::Packet::TreeConnectResponse.new
|
8
45
|
response.smb2_header.credits = 1
|
@@ -13,7 +50,7 @@ module RubySMB
|
|
13
50
|
end
|
14
51
|
|
15
52
|
share_name = request.path.encode('UTF-8').split('\\', 4).last
|
16
|
-
share_provider = @server.shares[share_name]
|
53
|
+
share_provider = @server.shares.transform_keys(&:downcase)[share_name.downcase]
|
17
54
|
|
18
55
|
if share_provider.nil?
|
19
56
|
logger.warn("Received TREE_CONNECT request for non-existent share: #{share_name}")
|
@@ -31,8 +68,8 @@ module RubySMB
|
|
31
68
|
RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_PRINT
|
32
69
|
end
|
33
70
|
|
34
|
-
tree_id = rand(
|
35
|
-
tree_id = rand(
|
71
|
+
tree_id = rand(1..0xfffffffe)
|
72
|
+
tree_id = rand(1..0xfffffffe) while session.tree_connect_table.include?(tree_id)
|
36
73
|
|
37
74
|
response.smb2_header.tree_id = tree_id
|
38
75
|
session.tree_connect_table[tree_id] = share_processor = share_provider.new_processor(self, session)
|
@@ -6,12 +6,14 @@ module RubySMB
|
|
6
6
|
|
7
7
|
require 'ruby_smb/dialect'
|
8
8
|
require 'ruby_smb/signing'
|
9
|
+
require 'ruby_smb/server/server_client/encryption'
|
9
10
|
require 'ruby_smb/server/server_client/negotiation'
|
10
11
|
require 'ruby_smb/server/server_client/session_setup'
|
11
12
|
require 'ruby_smb/server/server_client/share_io'
|
12
13
|
require 'ruby_smb/server/server_client/tree_connect'
|
13
14
|
|
14
15
|
include RubySMB::Signing
|
16
|
+
include RubySMB::Server::ServerClient::Encryption
|
15
17
|
include RubySMB::Server::ServerClient::Negotiation
|
16
18
|
include RubySMB::Server::ServerClient::SessionSetup
|
17
19
|
include RubySMB::Server::ServerClient::ShareIO
|
@@ -25,6 +27,8 @@ module RubySMB
|
|
25
27
|
@server = server
|
26
28
|
@dispatcher = dispatcher
|
27
29
|
@dialect = nil
|
30
|
+
@sequence_counter = 0
|
31
|
+
@cipher_id = 0
|
28
32
|
@gss_authenticator = server.gss_provider.new_authenticator(self)
|
29
33
|
@preauth_integrity_hash_algorithm = nil
|
30
34
|
@preauth_integrity_hash_value = nil
|
@@ -75,8 +79,10 @@ module RubySMB
|
|
75
79
|
|
76
80
|
begin
|
77
81
|
response = handle_smb1(raw_request, header)
|
78
|
-
rescue NotImplementedError
|
79
|
-
|
82
|
+
rescue NotImplementedError => e
|
83
|
+
message = "Caught a NotImplementedError while handling a #{SMB1::Commands.name(header.command)} request"
|
84
|
+
message << " (#{e.message})" if e.message
|
85
|
+
logger.error(message)
|
80
86
|
response = RubySMB::SMB1::Packet::EmptyPacket.new
|
81
87
|
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED
|
82
88
|
end
|
@@ -86,6 +92,12 @@ module RubySMB
|
|
86
92
|
if response.is_a?(SMB1::Packet::EmptyPacket)
|
87
93
|
response.smb_header.command = header.command if response.smb_header.command == 0
|
88
94
|
response.smb_header.flags.reply = 1
|
95
|
+
nt_status = response.smb_header.nt_status.to_i
|
96
|
+
message = "Sending an error packet for SMB1 command: #{SMB1::Commands.name(header.command)}, status: 0x#{nt_status.to_s(16).rjust(8, '0')}"
|
97
|
+
if (nt_status_name = WindowsError::NTStatus.find_by_retval(nt_status).first&.name)
|
98
|
+
message << " (#{nt_status_name})"
|
99
|
+
end
|
100
|
+
logger.info(message)
|
89
101
|
end
|
90
102
|
|
91
103
|
response.smb_header.pid_high = header.pid_high if response.smb_header.pid_high == 0
|
@@ -95,33 +107,23 @@ module RubySMB
|
|
95
107
|
response.smb_header.mid = header.mid if response.smb_header.mid == 0
|
96
108
|
end
|
97
109
|
when RubySMB::SMB2::SMB2_PROTOCOL_ID
|
110
|
+
response = _handle_smb2(raw_request)
|
111
|
+
when RubySMB::SMB2::SMB2_TRANSFORM_PROTOCOL_ID
|
98
112
|
begin
|
99
|
-
header = RubySMB::SMB2::
|
113
|
+
header = RubySMB::SMB2::Packet::TransformHeader.read(raw_request)
|
100
114
|
rescue IOError => e
|
101
|
-
logger.error("Caught a #{e.class} while reading the
|
115
|
+
logger.error("Caught a #{e.class} while reading the SMB3 Transform header")
|
102
116
|
disconnect!
|
103
117
|
return
|
104
118
|
end
|
105
119
|
|
106
120
|
begin
|
107
|
-
response =
|
121
|
+
response = handle_smb3_transform(raw_request, header)
|
108
122
|
rescue NotImplementedError
|
109
|
-
logger.error("Caught a NotImplementedError while handling a
|
123
|
+
logger.error("Caught a NotImplementedError while handling a SMB3 Transform request")
|
110
124
|
response = SMB2::Packet::ErrorPacket.new
|
111
125
|
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED
|
112
|
-
|
113
|
-
|
114
|
-
unless response.nil?
|
115
|
-
# set these header fields if they were not initialized
|
116
|
-
if response.is_a?(SMB2::Packet::ErrorPacket)
|
117
|
-
response.smb2_header.command = header.command if response.smb2_header.command == 0
|
118
|
-
response.smb2_header.flags.reply = 1
|
119
|
-
end
|
120
|
-
|
121
|
-
response.smb2_header.credits = 1 if response.smb2_header.credits == 0
|
122
|
-
response.smb2_header.message_id = header.message_id if response.smb2_header.message_id == 0
|
123
|
-
response.smb2_header.session_id = header.session_id if response.smb2_header.session_id == 0
|
124
|
-
response.smb2_header.tree_id = header.tree_id if response.smb2_header.tree_id == 0
|
126
|
+
response.smb2_header.session_id = header.session_id
|
125
127
|
end
|
126
128
|
end
|
127
129
|
|
@@ -193,17 +195,8 @@ module RubySMB
|
|
193
195
|
|
194
196
|
packet = @dispatcher.recv_packet
|
195
197
|
if packet && packet.length >= 4 && packet[0...4].unpack1('L>') == RubySMB::SMB2::SMB2_PROTOCOL_ID
|
196
|
-
|
197
|
-
|
198
|
-
until header.next_command == 0
|
199
|
-
@in_packet_queue.push(packet[0...header.next_command])
|
200
|
-
packet = packet[header.next_command..-1]
|
201
|
-
header = RubySMB::SMB2::SMB2Header.read(packet)
|
202
|
-
end
|
203
|
-
|
204
|
-
@in_packet_queue.push(packet)
|
205
|
-
packet = @in_packet_queue.shift
|
206
|
-
end
|
198
|
+
@in_packet_queue += split_smb2_chain(packet)
|
199
|
+
packet = @in_packet_queue.shift
|
207
200
|
end
|
208
201
|
|
209
202
|
packet
|
@@ -214,16 +207,24 @@ module RubySMB
|
|
214
207
|
#
|
215
208
|
# @param [GenericPacket] packet the packet to send
|
216
209
|
def send_packet(packet)
|
217
|
-
case metadialect&.
|
218
|
-
when Dialect::
|
210
|
+
case metadialect&.family
|
211
|
+
when Dialect::FAMILY_SMB1
|
219
212
|
session_id = packet.smb_header.uid
|
220
|
-
when Dialect::
|
213
|
+
when Dialect::FAMILY_SMB2
|
221
214
|
session_id = packet.smb2_header.session_id
|
215
|
+
when Dialect::FAMILY_SMB3
|
216
|
+
if packet.is_a?(RubySMB::SMB2::Packet::TransformHeader)
|
217
|
+
session_id = packet.session_id
|
218
|
+
else
|
219
|
+
session_id = packet.smb2_header.session_id
|
220
|
+
end
|
222
221
|
end
|
223
222
|
session = @session_table[session_id]
|
224
223
|
|
225
|
-
unless session.nil? || session.is_anonymous || session.key.nil?
|
224
|
+
unless session.nil? || session.is_anonymous || session.key.nil? || packet.is_a?(RubySMB::SMB2::Packet::TransformHeader)
|
226
225
|
case metadialect&.family
|
226
|
+
when Dialect::FAMILY_SMB1
|
227
|
+
packet = Signing::smb1_sign(packet, session.key, @sequence_counter)
|
227
228
|
when Dialect::FAMILY_SMB2
|
228
229
|
packet = Signing::smb2_sign(packet, session.key)
|
229
230
|
when Dialect::FAMILY_SMB3
|
@@ -231,6 +232,7 @@ module RubySMB
|
|
231
232
|
end
|
232
233
|
end
|
233
234
|
|
235
|
+
@sequence_counter += 1
|
234
236
|
@dispatcher.send_packet(packet)
|
235
237
|
end
|
236
238
|
|
@@ -260,12 +262,36 @@ module RubySMB
|
|
260
262
|
# @param [RubySMB::SMB1::SMBHeader] header The request header.
|
261
263
|
# @return [RubySMB::GenericPacket]
|
262
264
|
def handle_smb1(raw_request, header)
|
263
|
-
|
264
|
-
|
265
|
+
session = @session_table[header.uid]
|
266
|
+
|
267
|
+
if session.nil? && !(header.command == SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX && header.uid == 0)
|
268
|
+
response = SMB1::Packet::EmptyPacket.new
|
269
|
+
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_USER_SESSION_DELETED
|
270
|
+
return response
|
271
|
+
end
|
272
|
+
if session&.state == :expired
|
273
|
+
response = SMB1::Packet::EmptyPacket.new
|
274
|
+
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_NETWORK_SESSION_EXPIRED
|
275
|
+
return response
|
276
|
+
end
|
265
277
|
|
266
278
|
case header.command
|
279
|
+
when SMB1::Commands::SMB_COM_CLOSE
|
280
|
+
dispatcher, request_class = :do_close_smb1, SMB1::Packet::CloseRequest
|
281
|
+
when SMB1::Commands::SMB_COM_TREE_DISCONNECT
|
282
|
+
dispatcher, request_class = :do_tree_disconnect_smb1, SMB1::Packet::TreeDisconnectRequest
|
283
|
+
when SMB1::Commands::SMB_COM_LOGOFF_ANDX
|
284
|
+
dispatcher, request_class = :do_logoff_andx_smb1, SMB1::Packet::LogoffRequest
|
285
|
+
when SMB1::Commands::SMB_COM_NT_CREATE_ANDX
|
286
|
+
dispatcher, request_class = :do_nt_create_andx_smb1, SMB1::Packet::NtCreateAndxRequest
|
287
|
+
when SMB1::Commands::SMB_COM_READ_ANDX
|
288
|
+
dispatcher, request_class = :do_read_andx_smb1, SMB1::Packet::ReadAndxRequest
|
267
289
|
when SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX
|
268
|
-
dispatcher, request_class = :
|
290
|
+
dispatcher, request_class = :do_session_setup_andx_smb1, SMB1::Packet::SessionSetupRequest
|
291
|
+
when SMB1::Commands::SMB_COM_TRANSACTION2
|
292
|
+
dispatcher, request_class = :do_transactions2_smb1, SMB1::Packet::Trans2::Request
|
293
|
+
when SMB1::Commands::SMB_COM_TREE_CONNECT
|
294
|
+
dispatcher, request_class = :do_tree_connect_smb1, SMB1::Packet::TreeConnectRequest
|
269
295
|
else
|
270
296
|
logger.warn("The SMB1 #{SMB1::Commands.name(header.command)} command is not supported")
|
271
297
|
raise NotImplementedError
|
@@ -276,10 +302,13 @@ module RubySMB
|
|
276
302
|
rescue IOError, RubySMB::Error::InvalidPacket => e
|
277
303
|
logger.error("Caught a #{e.class} while reading the SMB1 #{request_class} (#{e.message})")
|
278
304
|
response = RubySMB::SMB1::Packet::EmptyPacket.new
|
305
|
+
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_DATA_ERROR
|
306
|
+
return response
|
279
307
|
end
|
280
308
|
|
281
309
|
if request.is_a?(SMB1::Packet::EmptyPacket)
|
282
310
|
logger.error("Received an error packet for SMB1 command: #{SMB1::Commands.name(header.command)}")
|
311
|
+
response = RubySMB::SMB1::Packet::EmptyPacket.new
|
283
312
|
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_DATA_ERROR
|
284
313
|
return response
|
285
314
|
end
|
@@ -304,6 +333,11 @@ module RubySMB
|
|
304
333
|
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_USER_SESSION_DELETED
|
305
334
|
return response
|
306
335
|
end
|
336
|
+
if session&.state == :expired
|
337
|
+
response = SMB2::Packet::ErrorPacket.new
|
338
|
+
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NETWORK_SESSION_EXPIRED
|
339
|
+
return response
|
340
|
+
end
|
307
341
|
|
308
342
|
case header.command
|
309
343
|
when SMB2::Commands::CLOSE
|
@@ -347,6 +381,82 @@ module RubySMB
|
|
347
381
|
logger.debug("Dispatching request to #{dispatcher} (session: #{session.inspect})")
|
348
382
|
send(dispatcher, request, session)
|
349
383
|
end
|
384
|
+
|
385
|
+
def _handle_smb2(raw_request)
|
386
|
+
begin
|
387
|
+
header = RubySMB::SMB2::SMB2Header.read(raw_request)
|
388
|
+
rescue IOError => e
|
389
|
+
logger.error("Caught a #{e.class} while reading the SMB2 header (#{e.message})")
|
390
|
+
disconnect!
|
391
|
+
return
|
392
|
+
end
|
393
|
+
|
394
|
+
begin
|
395
|
+
response = handle_smb2(raw_request, header)
|
396
|
+
rescue NotImplementedError
|
397
|
+
logger.error("Caught a NotImplementedError while handling a #{SMB2::Commands.name(header.command)} request")
|
398
|
+
response = SMB2::Packet::ErrorPacket.new
|
399
|
+
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED
|
400
|
+
end
|
401
|
+
|
402
|
+
unless response.nil?
|
403
|
+
# set these header fields if they were not initialized
|
404
|
+
if response.is_a?(SMB2::Packet::ErrorPacket)
|
405
|
+
response.smb2_header.command = header.command if response.smb2_header.command == 0
|
406
|
+
response.smb2_header.flags.reply = 1
|
407
|
+
nt_status = response.smb2_header.nt_status.to_i
|
408
|
+
message = "Sending an error packet for SMB2 command: #{SMB2::Commands.name(header.command)}, status: 0x#{nt_status.to_s(16).rjust(8, '0')}"
|
409
|
+
if (nt_status_name = WindowsError::NTStatus.find_by_retval(nt_status).first&.name)
|
410
|
+
message << " (#{nt_status_name})"
|
411
|
+
end
|
412
|
+
logger.info(message)
|
413
|
+
end
|
414
|
+
|
415
|
+
response.smb2_header.credits = 1 if response.smb2_header.credits == 0
|
416
|
+
response.smb2_header.message_id = header.message_id if response.smb2_header.message_id == 0
|
417
|
+
response.smb2_header.session_id = header.session_id if response.smb2_header.session_id == 0
|
418
|
+
response.smb2_header.tree_id = header.tree_id if response.smb2_header.tree_id == 0
|
419
|
+
end
|
420
|
+
|
421
|
+
response
|
422
|
+
end
|
423
|
+
|
424
|
+
def handle_smb3_transform(raw_request, header)
|
425
|
+
session = @session_table[header.session_id]
|
426
|
+
if session.nil?
|
427
|
+
response = SMB2::Packet::ErrorPacket.new
|
428
|
+
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_USER_SESSION_DELETED
|
429
|
+
return response
|
430
|
+
end
|
431
|
+
|
432
|
+
chain = split_smb2_chain(smb3_decrypt(header, session))
|
433
|
+
chain[0...-1].each do |pt_raw_request|
|
434
|
+
pt_response = _handle_smb2(pt_raw_request)
|
435
|
+
return if pt_response.nil?
|
436
|
+
|
437
|
+
send_packet(smb3_encrypt(pt_response, session))
|
438
|
+
end
|
439
|
+
|
440
|
+
pt_response = _handle_smb2(chain.last)
|
441
|
+
return if pt_response.nil?
|
442
|
+
|
443
|
+
smb3_encrypt(pt_response, session)
|
444
|
+
end
|
445
|
+
|
446
|
+
def split_smb2_chain(buffer)
|
447
|
+
chain = []
|
448
|
+
header = RubySMB::SMB2::SMB2Header.read(buffer)
|
449
|
+
unless header.next_command == 0
|
450
|
+
until header.next_command == 0
|
451
|
+
chain << buffer[0...header.next_command]
|
452
|
+
buffer = buffer[header.next_command..-1]
|
453
|
+
header = RubySMB::SMB2::SMB2Header.read(buffer)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
chain << buffer
|
458
|
+
chain
|
459
|
+
end
|
350
460
|
end
|
351
461
|
end
|
352
462
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module RubySMB
|
2
|
+
class Server
|
3
|
+
module Share
|
4
|
+
module Provider
|
5
|
+
class Disk < Base
|
6
|
+
module FileSystem
|
7
|
+
# define attributes of the file system to emulate
|
8
|
+
FileSystem = Struct.new(
|
9
|
+
:name,
|
10
|
+
:max_name_bytes,
|
11
|
+
:case_sensitive_search,
|
12
|
+
:case_preserved_names,
|
13
|
+
:unicode_on_disk
|
14
|
+
)
|
15
|
+
|
16
|
+
NTFS = FileSystem.new(
|
17
|
+
'NTFS',
|
18
|
+
255,
|
19
|
+
true,
|
20
|
+
true,
|
21
|
+
true
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|