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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/ruby_smb/client/encryption.rb +16 -4
  4. data/lib/ruby_smb/client/negotiation.rb +3 -1
  5. data/lib/ruby_smb/fscc/file_information.rb +4 -0
  6. data/lib/ruby_smb/server/server_client/encryption.rb +66 -0
  7. data/lib/ruby_smb/server/server_client/negotiation.rb +14 -3
  8. data/lib/ruby_smb/server/server_client/session_setup.rb +18 -3
  9. data/lib/ruby_smb/server/server_client/share_io.rb +17 -0
  10. data/lib/ruby_smb/server/server_client/tree_connect.rb +40 -3
  11. data/lib/ruby_smb/server/server_client.rb +147 -37
  12. data/lib/ruby_smb/server/share/provider/disk/file_system.rb +28 -0
  13. data/lib/ruby_smb/server/share/provider/disk/processor/close.rb +42 -0
  14. data/lib/ruby_smb/server/share/provider/disk/processor/create.rb +143 -0
  15. data/lib/ruby_smb/server/share/provider/disk/processor/query.rb +359 -0
  16. data/lib/ruby_smb/server/share/provider/disk/processor/read.rb +69 -0
  17. data/lib/ruby_smb/server/share/provider/disk/processor.rb +159 -0
  18. data/lib/ruby_smb/server/share/provider/disk.rb +4 -416
  19. data/lib/ruby_smb/server/share/provider/pipe.rb +2 -2
  20. data/lib/ruby_smb/server/share/provider/processor.rb +16 -0
  21. data/lib/ruby_smb/signing.rb +18 -4
  22. data/lib/ruby_smb/smb1/commands.rb +1 -0
  23. data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +11 -1
  24. data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +1 -1
  25. data/lib/ruby_smb/smb1/packet/read_andx_response.rb +5 -4
  26. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +12 -4
  27. data/lib/ruby_smb/smb1/packet/trans2/data_block.rb +9 -1
  28. data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +52 -51
  29. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +37 -37
  30. data/lib/ruby_smb/smb1/packet/trans2/find_information_level/find_file_both_directory_info.rb +48 -0
  31. data/lib/ruby_smb/smb1/packet/trans2/find_information_level.rb +28 -15
  32. data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +51 -51
  33. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +36 -36
  34. data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +40 -39
  35. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +40 -40
  36. data/lib/ruby_smb/smb1/packet/trans2/query_file_information_request.rb +60 -0
  37. data/lib/ruby_smb/smb1/packet/trans2/query_file_information_response.rb +59 -0
  38. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level/query_fs_attribute_info.rb +31 -0
  39. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level.rb +40 -0
  40. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request.rb +46 -0
  41. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response.rb +59 -0
  42. data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_basic_info.rb +23 -0
  43. data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_standard_info.rb +22 -0
  44. data/lib/ruby_smb/smb1/packet/trans2/query_information_level.rb +62 -0
  45. data/lib/ruby_smb/smb1/packet/trans2/query_path_information_request.rb +65 -0
  46. data/lib/ruby_smb/smb1/packet/trans2/query_path_information_response.rb +59 -0
  47. data/lib/ruby_smb/smb1/packet/trans2/request.rb +24 -8
  48. data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +4 -4
  49. data/lib/ruby_smb/smb1/packet/trans2/response.rb +29 -20
  50. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +42 -42
  51. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +23 -23
  52. data/lib/ruby_smb/smb1/packet/trans2/subcommands.rb +23 -5
  53. data/lib/ruby_smb/smb1/packet/trans2.rb +4 -0
  54. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +4 -1
  55. data/lib/ruby_smb/smb2/negotiate_context.rb +10 -1
  56. data/lib/ruby_smb/smb2/packet/transform_header.rb +7 -7
  57. data/lib/ruby_smb/smb2.rb +1 -0
  58. data/lib/ruby_smb/version.rb +1 -1
  59. data/spec/lib/ruby_smb/client_spec.rb +20 -6
  60. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_request_spec.rb +2 -2
  61. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +36 -2
  62. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_request_spec.rb +2 -2
  63. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +35 -1
  64. data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_request_spec.rb +74 -0
  65. data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_response_spec.rb +96 -0
  66. data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request_spec.rb +62 -0
  67. data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response_spec.rb +88 -0
  68. data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_request_spec.rb +79 -0
  69. data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_response_spec.rb +96 -0
  70. data/spec/lib/ruby_smb/smb1/packet/trans2/request_spec.rb +2 -2
  71. data/spec/lib/ruby_smb/smb1/packet/trans2/response_spec.rb +3 -3
  72. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_request_spec.rb +3 -2
  73. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb +7 -2
  74. data/spec/lib/ruby_smb/smb1/tree_spec.rb +3 -3
  75. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +2 -2
  76. data.tar.gz.sig +0 -0
  77. metadata +33 -2
  78. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4dd0be012c18ad4e243cbfa2948fc620448766612b2f33be278583981fa2cf90
4
- data.tar.gz: '07309ce54e701f2c3654a9cdbc8b3eff9d42d54451321e6e17a49d46ae4ce162'
3
+ metadata.gz: 582c72fe6559ee4c40aa9e33f77c6644669848ae3452c11095f31fb0b0fce387
4
+ data.tar.gz: 2791bfa79cb55e6ec6689782a747860287278bc42c38d6ecd03a66396efba200
5
5
  SHA512:
6
- metadata.gz: ee506f05e67f8fa1a61b0d0f05470b41e20c5972c9c9584e8fe8069352a2cd09704b66d42856fb1330a93def276525b0d50047e93b981479c064c36f35f8298a
7
- data.tar.gz: 78c37f1157393e17c51a6de7ff55fb25206e84f68740c958c406c36432ca90bc572716670640b9153a3ff66247dfcbeae8f4305f3c17cd91af64e8a4fc092894
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::AES_128_CCM
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
- cipher = nc&.data&.ciphers&.first
123
- cipher = 0 unless SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP.include? cipher
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: [ cipher ]
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 do_session_setup_smb1(request, session)
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
- session = @session_table.delete(session.id)
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(0xffffffff)
35
- tree_id = rand(0xffffffff) while session.tree_connect_table.include?(tree_id)
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
- logger.error("Caught a NotImplementedError while handling a #{SMB1::Commands.name(header.command)} request")
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::SMB2Header.read(raw_request)
113
+ header = RubySMB::SMB2::Packet::TransformHeader.read(raw_request)
100
114
  rescue IOError => e
101
- logger.error("Caught a #{e.class} while reading the SMB2 header (#{e.message})")
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 = handle_smb2(raw_request, header)
121
+ response = handle_smb3_transform(raw_request, header)
108
122
  rescue NotImplementedError
109
- logger.error("Caught a NotImplementedError while handling a #{SMB2::Commands.name(header.command)} request")
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
- end
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
- header = RubySMB::SMB2::SMB2Header.read(packet)
197
- unless header.next_command == 0
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&.order
218
- when Dialect::ORDER_SMB1
210
+ case metadialect&.family
211
+ when Dialect::FAMILY_SMB1
219
212
  session_id = packet.smb_header.uid
220
- when Dialect::ORDER_SMB2
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
- # session = @session_table[header.uid]
264
- session = nil
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 = :do_session_setup_smb1, SMB1::Packet::SessionSetupRequest
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