ruby_smb 3.0.6 → 3.1.2

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 (140) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/examples/file_server.rb +8 -1
  4. data/examples/virtual_file_server.rb +143 -0
  5. data/lib/ruby_smb/client/encryption.rb +16 -4
  6. data/lib/ruby_smb/client/negotiation.rb +10 -8
  7. data/lib/ruby_smb/fscc/file_information/file_access_information.rb +15 -0
  8. data/lib/ruby_smb/fscc/file_information/file_alignment_information.rb +45 -0
  9. data/lib/ruby_smb/fscc/file_information/file_all_information.rb +23 -0
  10. data/lib/ruby_smb/fscc/file_information/file_basic_information.rb +20 -0
  11. data/lib/ruby_smb/fscc/file_information/file_both_directory_information.rb +3 -3
  12. data/lib/ruby_smb/fscc/file_information/file_directory_information.rb +3 -3
  13. data/lib/ruby_smb/fscc/file_information/file_ea_information.rb +1 -0
  14. data/lib/ruby_smb/fscc/file_information/file_full_directory_information.rb +3 -3
  15. data/lib/ruby_smb/fscc/file_information/file_id_both_directory_information.rb +3 -3
  16. data/lib/ruby_smb/fscc/file_information/file_id_full_directory_information.rb +3 -3
  17. data/lib/ruby_smb/fscc/file_information/file_internal_information.rb +15 -0
  18. data/lib/ruby_smb/fscc/file_information/file_mode_information.rb +29 -0
  19. data/lib/ruby_smb/fscc/file_information/file_name_information.rb +16 -0
  20. data/lib/ruby_smb/fscc/file_information/file_names_information.rb +1 -1
  21. data/lib/ruby_smb/fscc/file_information/file_normalized_name_information.rb +16 -0
  22. data/lib/ruby_smb/fscc/file_information/file_position_information.rb +15 -0
  23. data/lib/ruby_smb/fscc/file_information/file_rename_information.rb +1 -1
  24. data/lib/ruby_smb/fscc/file_information/file_standard_information.rb +20 -0
  25. data/lib/ruby_smb/fscc/file_information/file_stream_information.rb +3 -0
  26. data/lib/ruby_smb/fscc/file_information.rb +43 -6
  27. data/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information.rb +1 -0
  28. data/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information.rb +1 -0
  29. data/lib/ruby_smb/fscc/file_system_information.rb +4 -0
  30. data/lib/ruby_smb/gss/provider/ntlm.rb +20 -3
  31. data/lib/ruby_smb/gss/provider.rb +10 -1
  32. data/lib/ruby_smb/server/server_client/encryption.rb +66 -0
  33. data/lib/ruby_smb/server/server_client/negotiation.rb +14 -3
  34. data/lib/ruby_smb/server/server_client/session_setup.rb +21 -4
  35. data/lib/ruby_smb/server/server_client/share_io.rb +17 -0
  36. data/lib/ruby_smb/server/server_client/tree_connect.rb +40 -3
  37. data/lib/ruby_smb/server/server_client.rb +156 -38
  38. data/lib/ruby_smb/server/session.rb +5 -1
  39. data/lib/ruby_smb/server/share/provider/disk/file_system.rb +28 -0
  40. data/lib/ruby_smb/server/share/provider/disk/processor/close.rb +46 -0
  41. data/lib/ruby_smb/server/share/provider/disk/processor/create.rb +143 -0
  42. data/lib/ruby_smb/server/share/provider/disk/processor/query.rb +359 -0
  43. data/lib/ruby_smb/server/share/provider/disk/processor/read.rb +70 -0
  44. data/lib/ruby_smb/server/share/provider/disk/processor.rb +223 -0
  45. data/lib/ruby_smb/server/share/provider/disk.rb +12 -418
  46. data/lib/ruby_smb/server/share/provider/pipe.rb +2 -2
  47. data/lib/ruby_smb/server/share/provider/processor.rb +16 -0
  48. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_file.rb +85 -0
  49. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname.rb +196 -0
  50. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat.rb +175 -0
  51. data/lib/ruby_smb/server/share/provider/virtual_disk.rb +116 -0
  52. data/lib/ruby_smb/server/share/provider.rb +1 -0
  53. data/lib/ruby_smb/server.rb +13 -3
  54. data/lib/ruby_smb/signing.rb +18 -4
  55. data/lib/ruby_smb/smb1/commands.rb +1 -0
  56. data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +11 -1
  57. data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +1 -1
  58. data/lib/ruby_smb/smb1/packet/read_andx_response.rb +5 -4
  59. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +12 -4
  60. data/lib/ruby_smb/smb1/packet/trans2/data_block.rb +9 -1
  61. data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +52 -51
  62. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +37 -37
  63. data/lib/ruby_smb/smb1/packet/trans2/find_information_level/find_file_both_directory_info.rb +48 -0
  64. data/lib/ruby_smb/smb1/packet/trans2/find_information_level.rb +28 -15
  65. data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +51 -51
  66. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +36 -36
  67. data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +40 -39
  68. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +40 -40
  69. data/lib/ruby_smb/smb1/packet/trans2/query_file_information_request.rb +60 -0
  70. data/lib/ruby_smb/smb1/packet/trans2/query_file_information_response.rb +59 -0
  71. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level/query_fs_attribute_info.rb +31 -0
  72. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level.rb +40 -0
  73. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request.rb +46 -0
  74. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response.rb +59 -0
  75. data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_basic_info.rb +23 -0
  76. data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_standard_info.rb +22 -0
  77. data/lib/ruby_smb/smb1/packet/trans2/query_information_level.rb +62 -0
  78. data/lib/ruby_smb/smb1/packet/trans2/query_path_information_request.rb +65 -0
  79. data/lib/ruby_smb/smb1/packet/trans2/query_path_information_response.rb +59 -0
  80. data/lib/ruby_smb/smb1/packet/trans2/request.rb +24 -8
  81. data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +4 -4
  82. data/lib/ruby_smb/smb1/packet/trans2/response.rb +29 -20
  83. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +42 -42
  84. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +23 -23
  85. data/lib/ruby_smb/smb1/packet/trans2/subcommands.rb +23 -5
  86. data/lib/ruby_smb/smb1/packet/trans2.rb +4 -0
  87. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +4 -1
  88. data/lib/ruby_smb/smb2/negotiate_context.rb +10 -1
  89. data/lib/ruby_smb/smb2/packet/transform_header.rb +7 -7
  90. data/lib/ruby_smb/smb2/tree.rb +1 -0
  91. data/lib/ruby_smb/smb2.rb +1 -0
  92. data/lib/ruby_smb/version.rb +1 -1
  93. data/spec/lib/ruby_smb/client_spec.rb +31 -8
  94. data/spec/lib/ruby_smb/fscc/file_information/file_access_information_spec.rb +21 -0
  95. data/spec/lib/ruby_smb/fscc/file_information/file_alignment_information_spec.rb +21 -0
  96. data/spec/lib/ruby_smb/fscc/file_information/file_all_information_spec.rb +61 -0
  97. data/spec/lib/ruby_smb/fscc/file_information/file_basic_information_spec.rb +41 -0
  98. data/spec/lib/ruby_smb/fscc/file_information/file_both_directory_information_spec.rb +59 -10
  99. data/spec/lib/ruby_smb/fscc/file_information/file_directory_information_spec.rb +30 -12
  100. data/spec/lib/ruby_smb/fscc/file_information/file_ea_information_spec.rb +21 -0
  101. data/spec/lib/ruby_smb/fscc/file_information/file_full_directory_information_spec.rb +30 -12
  102. data/spec/lib/ruby_smb/fscc/file_information/file_id_both_directory_information_spec.rb +63 -10
  103. data/spec/lib/ruby_smb/fscc/file_information/file_id_full_directory_information_spec.rb +30 -12
  104. data/spec/lib/ruby_smb/fscc/file_information/file_internal_information_spec.rb +21 -0
  105. data/spec/lib/ruby_smb/fscc/file_information/file_mode_information_spec.rb +21 -0
  106. data/spec/lib/ruby_smb/fscc/file_information/file_name_information_spec.rb +44 -0
  107. data/spec/lib/ruby_smb/fscc/file_information/file_names_information_spec.rb +30 -12
  108. data/spec/lib/ruby_smb/fscc/file_information/file_network_open_information_spec.rb +51 -0
  109. data/spec/lib/ruby_smb/fscc/file_information/file_normalized_name_information_spec.rb +44 -0
  110. data/spec/lib/ruby_smb/fscc/file_information/file_position_information_spec.rb +21 -0
  111. data/spec/lib/ruby_smb/fscc/file_information/file_rename_information_spec.rb +1 -1
  112. data/spec/lib/ruby_smb/fscc/file_information/file_standard_information_spec.rb +41 -0
  113. data/spec/lib/ruby_smb/fscc/file_information/file_stream_information_spec.rb +51 -0
  114. data/spec/lib/ruby_smb/fscc/file_information_spec.rb +14 -0
  115. data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information_spec.rb +46 -0
  116. data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information_spec.rb +51 -0
  117. data/spec/lib/ruby_smb/fscc/file_system_information_spec.rb +14 -0
  118. data/spec/lib/ruby_smb/server/server_client_spec.rb +15 -0
  119. data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname_spec.rb +581 -0
  120. data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat_spec.rb +207 -0
  121. data/spec/lib/ruby_smb/server/share/provider/virtual_disk_spec.rb +122 -0
  122. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_request_spec.rb +2 -2
  123. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +36 -2
  124. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_request_spec.rb +2 -2
  125. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +35 -1
  126. data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_request_spec.rb +74 -0
  127. data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_response_spec.rb +96 -0
  128. data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request_spec.rb +62 -0
  129. data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response_spec.rb +88 -0
  130. data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_request_spec.rb +79 -0
  131. data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_response_spec.rb +96 -0
  132. data/spec/lib/ruby_smb/smb1/packet/trans2/request_spec.rb +2 -2
  133. data/spec/lib/ruby_smb/smb1/packet/trans2/response_spec.rb +3 -3
  134. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_request_spec.rb +3 -2
  135. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb +7 -2
  136. data/spec/lib/ruby_smb/smb1/tree_spec.rb +3 -3
  137. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +2 -2
  138. data.tar.gz.sig +0 -0
  139. metadata +88 -2
  140. metadata.gz.sig +0 -0
@@ -6,18 +6,20 @@ 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
18
20
  include RubySMB::Server::ServerClient::TreeConnect
19
21
 
20
- attr_reader :dialect, :session_table
22
+ attr_reader :dialect, :dispatcher, :session_table
21
23
 
22
24
  # @param [Server] server the server that accepted this connection
23
25
  # @param [Dispatcher::Socket] dispatcher the connection's socket dispatcher
@@ -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
@@ -53,6 +57,14 @@ module RubySMB
53
57
  @dispatcher.tcp_socket.getpeername
54
58
  end
55
59
 
60
+ def peerhost
61
+ ::Socket::unpack_sockaddr_in(getpeername)[1]
62
+ end
63
+
64
+ def peerport
65
+ ::Socket::unpack_sockaddr_in(getpeername)[0]
66
+ end
67
+
56
68
  #
57
69
  # Handle a request after the dialect has been negotiated. This is the main
58
70
  # handler for all requests after the connection has been established. If a
@@ -75,8 +87,10 @@ module RubySMB
75
87
 
76
88
  begin
77
89
  response = handle_smb1(raw_request, header)
78
- rescue NotImplementedError
79
- logger.error("Caught a NotImplementedError while handling a #{SMB1::Commands.name(header.command)} request")
90
+ rescue NotImplementedError => e
91
+ message = "Caught a NotImplementedError while handling a #{SMB1::Commands.name(header.command)} request"
92
+ message << " (#{e.message})" if e.message
93
+ logger.error(message)
80
94
  response = RubySMB::SMB1::Packet::EmptyPacket.new
81
95
  response.smb_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED
82
96
  end
@@ -86,6 +100,12 @@ module RubySMB
86
100
  if response.is_a?(SMB1::Packet::EmptyPacket)
87
101
  response.smb_header.command = header.command if response.smb_header.command == 0
88
102
  response.smb_header.flags.reply = 1
103
+ nt_status = response.smb_header.nt_status.to_i
104
+ message = "Sending an error packet for SMB1 command: #{SMB1::Commands.name(header.command)}, status: 0x#{nt_status.to_s(16).rjust(8, '0')}"
105
+ if (nt_status_name = WindowsError::NTStatus.find_by_retval(nt_status).first&.name)
106
+ message << " (#{nt_status_name})"
107
+ end
108
+ logger.info(message)
89
109
  end
90
110
 
91
111
  response.smb_header.pid_high = header.pid_high if response.smb_header.pid_high == 0
@@ -95,33 +115,23 @@ module RubySMB
95
115
  response.smb_header.mid = header.mid if response.smb_header.mid == 0
96
116
  end
97
117
  when RubySMB::SMB2::SMB2_PROTOCOL_ID
118
+ response = _handle_smb2(raw_request)
119
+ when RubySMB::SMB2::SMB2_TRANSFORM_PROTOCOL_ID
98
120
  begin
99
- header = RubySMB::SMB2::SMB2Header.read(raw_request)
121
+ header = RubySMB::SMB2::Packet::TransformHeader.read(raw_request)
100
122
  rescue IOError => e
101
- logger.error("Caught a #{e.class} while reading the SMB2 header (#{e.message})")
123
+ logger.error("Caught a #{e.class} while reading the SMB3 Transform header")
102
124
  disconnect!
103
125
  return
104
126
  end
105
127
 
106
128
  begin
107
- response = handle_smb2(raw_request, header)
129
+ response = handle_smb3_transform(raw_request, header)
108
130
  rescue NotImplementedError
109
- logger.error("Caught a NotImplementedError while handling a #{SMB2::Commands.name(header.command)} request")
131
+ logger.error("Caught a NotImplementedError while handling a SMB3 Transform request")
110
132
  response = SMB2::Packet::ErrorPacket.new
111
133
  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
134
+ response.smb2_header.session_id = header.session_id
125
135
  end
126
136
  end
127
137
 
@@ -193,17 +203,8 @@ module RubySMB
193
203
 
194
204
  packet = @dispatcher.recv_packet
195
205
  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
206
+ @in_packet_queue += split_smb2_chain(packet)
207
+ packet = @in_packet_queue.shift
207
208
  end
208
209
 
209
210
  packet
@@ -214,16 +215,24 @@ module RubySMB
214
215
  #
215
216
  # @param [GenericPacket] packet the packet to send
216
217
  def send_packet(packet)
217
- case metadialect&.order
218
- when Dialect::ORDER_SMB1
218
+ case metadialect&.family
219
+ when Dialect::FAMILY_SMB1
219
220
  session_id = packet.smb_header.uid
220
- when Dialect::ORDER_SMB2
221
+ when Dialect::FAMILY_SMB2
221
222
  session_id = packet.smb2_header.session_id
223
+ when Dialect::FAMILY_SMB3
224
+ if packet.is_a?(RubySMB::SMB2::Packet::TransformHeader)
225
+ session_id = packet.session_id
226
+ else
227
+ session_id = packet.smb2_header.session_id
228
+ end
222
229
  end
223
230
  session = @session_table[session_id]
224
231
 
225
- unless session.nil? || session.is_anonymous || session.key.nil?
232
+ unless session.nil? || session.is_anonymous || session.key.nil? || packet.is_a?(RubySMB::SMB2::Packet::TransformHeader)
226
233
  case metadialect&.family
234
+ when Dialect::FAMILY_SMB1
235
+ packet = Signing::smb1_sign(packet, session.key, @sequence_counter)
227
236
  when Dialect::FAMILY_SMB2
228
237
  packet = Signing::smb2_sign(packet, session.key)
229
238
  when Dialect::FAMILY_SMB3
@@ -231,6 +240,7 @@ module RubySMB
231
240
  end
232
241
  end
233
242
 
243
+ @sequence_counter += 1
234
244
  @dispatcher.send_packet(packet)
235
245
  end
236
246
 
@@ -260,12 +270,36 @@ module RubySMB
260
270
  # @param [RubySMB::SMB1::SMBHeader] header The request header.
261
271
  # @return [RubySMB::GenericPacket]
262
272
  def handle_smb1(raw_request, header)
263
- # session = @session_table[header.uid]
264
- session = nil
273
+ session = @session_table[header.uid]
274
+
275
+ if session.nil? && !(header.command == SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX && header.uid == 0)
276
+ response = SMB1::Packet::EmptyPacket.new
277
+ response.smb_header.nt_status = WindowsError::NTStatus::STATUS_USER_SESSION_DELETED
278
+ return response
279
+ end
280
+ if session&.state == :expired
281
+ response = SMB1::Packet::EmptyPacket.new
282
+ response.smb_header.nt_status = WindowsError::NTStatus::STATUS_NETWORK_SESSION_EXPIRED
283
+ return response
284
+ end
265
285
 
266
286
  case header.command
287
+ when SMB1::Commands::SMB_COM_CLOSE
288
+ dispatcher, request_class = :do_close_smb1, SMB1::Packet::CloseRequest
289
+ when SMB1::Commands::SMB_COM_TREE_DISCONNECT
290
+ dispatcher, request_class = :do_tree_disconnect_smb1, SMB1::Packet::TreeDisconnectRequest
291
+ when SMB1::Commands::SMB_COM_LOGOFF_ANDX
292
+ dispatcher, request_class = :do_logoff_andx_smb1, SMB1::Packet::LogoffRequest
293
+ when SMB1::Commands::SMB_COM_NT_CREATE_ANDX
294
+ dispatcher, request_class = :do_nt_create_andx_smb1, SMB1::Packet::NtCreateAndxRequest
295
+ when SMB1::Commands::SMB_COM_READ_ANDX
296
+ dispatcher, request_class = :do_read_andx_smb1, SMB1::Packet::ReadAndxRequest
267
297
  when SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX
268
- dispatcher, request_class = :do_session_setup_smb1, SMB1::Packet::SessionSetupRequest
298
+ dispatcher, request_class = :do_session_setup_andx_smb1, SMB1::Packet::SessionSetupRequest
299
+ when SMB1::Commands::SMB_COM_TRANSACTION2
300
+ dispatcher, request_class = :do_transactions2_smb1, SMB1::Packet::Trans2::Request
301
+ when SMB1::Commands::SMB_COM_TREE_CONNECT
302
+ dispatcher, request_class = :do_tree_connect_smb1, SMB1::Packet::TreeConnectRequest
269
303
  else
270
304
  logger.warn("The SMB1 #{SMB1::Commands.name(header.command)} command is not supported")
271
305
  raise NotImplementedError
@@ -276,10 +310,13 @@ module RubySMB
276
310
  rescue IOError, RubySMB::Error::InvalidPacket => e
277
311
  logger.error("Caught a #{e.class} while reading the SMB1 #{request_class} (#{e.message})")
278
312
  response = RubySMB::SMB1::Packet::EmptyPacket.new
313
+ response.smb_header.nt_status = WindowsError::NTStatus::STATUS_DATA_ERROR
314
+ return response
279
315
  end
280
316
 
281
317
  if request.is_a?(SMB1::Packet::EmptyPacket)
282
318
  logger.error("Received an error packet for SMB1 command: #{SMB1::Commands.name(header.command)}")
319
+ response = RubySMB::SMB1::Packet::EmptyPacket.new
283
320
  response.smb_header.nt_status = WindowsError::NTStatus::STATUS_DATA_ERROR
284
321
  return response
285
322
  end
@@ -304,6 +341,11 @@ module RubySMB
304
341
  response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_USER_SESSION_DELETED
305
342
  return response
306
343
  end
344
+ if session&.state == :expired
345
+ response = SMB2::Packet::ErrorPacket.new
346
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NETWORK_SESSION_EXPIRED
347
+ return response
348
+ end
307
349
 
308
350
  case header.command
309
351
  when SMB2::Commands::CLOSE
@@ -347,6 +389,82 @@ module RubySMB
347
389
  logger.debug("Dispatching request to #{dispatcher} (session: #{session.inspect})")
348
390
  send(dispatcher, request, session)
349
391
  end
392
+
393
+ def _handle_smb2(raw_request)
394
+ begin
395
+ header = RubySMB::SMB2::SMB2Header.read(raw_request)
396
+ rescue IOError => e
397
+ logger.error("Caught a #{e.class} while reading the SMB2 header (#{e.message})")
398
+ disconnect!
399
+ return
400
+ end
401
+
402
+ begin
403
+ response = handle_smb2(raw_request, header)
404
+ rescue NotImplementedError
405
+ logger.error("Caught a NotImplementedError while handling a #{SMB2::Commands.name(header.command)} request")
406
+ response = SMB2::Packet::ErrorPacket.new
407
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED
408
+ end
409
+
410
+ unless response.nil?
411
+ # set these header fields if they were not initialized
412
+ if response.is_a?(SMB2::Packet::ErrorPacket)
413
+ response.smb2_header.command = header.command if response.smb2_header.command == 0
414
+ response.smb2_header.flags.reply = 1
415
+ nt_status = response.smb2_header.nt_status.to_i
416
+ message = "Sending an error packet for SMB2 command: #{SMB2::Commands.name(header.command)}, status: 0x#{nt_status.to_s(16).rjust(8, '0')}"
417
+ if (nt_status_name = WindowsError::NTStatus.find_by_retval(nt_status).first&.name)
418
+ message << " (#{nt_status_name})"
419
+ end
420
+ logger.info(message)
421
+ end
422
+
423
+ response.smb2_header.credits = 1 if response.smb2_header.credits == 0
424
+ response.smb2_header.message_id = header.message_id if response.smb2_header.message_id == 0
425
+ response.smb2_header.session_id = header.session_id if response.smb2_header.session_id == 0
426
+ response.smb2_header.tree_id = header.tree_id if response.smb2_header.tree_id == 0
427
+ end
428
+
429
+ response
430
+ end
431
+
432
+ def handle_smb3_transform(raw_request, header)
433
+ session = @session_table[header.session_id]
434
+ if session.nil?
435
+ response = SMB2::Packet::ErrorPacket.new
436
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_USER_SESSION_DELETED
437
+ return response
438
+ end
439
+
440
+ chain = split_smb2_chain(smb3_decrypt(header, session))
441
+ chain[0...-1].each do |pt_raw_request|
442
+ pt_response = _handle_smb2(pt_raw_request)
443
+ return if pt_response.nil?
444
+
445
+ send_packet(smb3_encrypt(pt_response, session))
446
+ end
447
+
448
+ pt_response = _handle_smb2(chain.last)
449
+ return if pt_response.nil?
450
+
451
+ smb3_encrypt(pt_response, session)
452
+ end
453
+
454
+ def split_smb2_chain(buffer)
455
+ chain = []
456
+ header = RubySMB::SMB2::SMB2Header.read(buffer)
457
+ unless header.next_command == 0
458
+ until header.next_command == 0
459
+ chain << buffer[0...header.next_command]
460
+ buffer = buffer[header.next_command..-1]
461
+ header = RubySMB::SMB2::SMB2Header.read(buffer)
462
+ end
463
+ end
464
+
465
+ chain << buffer
466
+ chain
467
+ end
350
468
  end
351
469
  end
352
470
  end
@@ -64,7 +64,7 @@ module RubySMB
64
64
  attr_accessor :tree_connect_table
65
65
 
66
66
  # Untyped hash for storing additional arbitrary metadata about the current session
67
- # @!attribute [rw] metadaa
67
+ # @!attribute [rw] metadata
68
68
  # @return [Hash]
69
69
  attr_accessor :metadata
70
70
 
@@ -72,6 +72,10 @@ module RubySMB
72
72
  # @!attribute [r] creation_time
73
73
  # @return [Time]
74
74
  attr_reader :creation_time
75
+
76
+ # Whether or not the authenticated user is a guest.
77
+ # @!attribute [rw] is_guest
78
+ attr_accessor :is_guest
75
79
  end
76
80
  end
77
81
  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
@@ -0,0 +1,46 @@
1
+ require 'ruby_smb/server/share/provider/processor'
2
+
3
+ module RubySMB
4
+ class Server
5
+ module Share
6
+ module Provider
7
+ class Disk < Base
8
+ class Processor < Provider::Processor::Base
9
+ module Close
10
+ def do_close_smb1(request)
11
+ # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/99b767e2-8f0e-438b-ace5-4323940f2dc8
12
+ handle = @handles.delete(request.parameter_block.fid)
13
+ if handle.nil?
14
+ response = RubySMB::SMB1::Packet::EmptyPacket.new
15
+ response.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE
16
+ return response
17
+ end
18
+
19
+ handle.file.close if handle.file
20
+
21
+ response = RubySMB::SMB1::Packet::CloseResponse.new
22
+ response
23
+ end
24
+
25
+ def do_close_smb2(request)
26
+ handle = @handles.delete(request.file_id.to_binary_s)
27
+ if handle.nil?
28
+ response = RubySMB::SMB2::Packet::ErrorPacket.new
29
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
30
+ return response
31
+ end
32
+
33
+ handle.file.close if handle.file
34
+
35
+ response = RubySMB::SMB2::Packet::CloseResponse.new
36
+ set_common_info(response, handle.local_path)
37
+ response.flags = 1
38
+ response
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,143 @@
1
+ require 'zlib'
2
+ require 'ruby_smb/server/share/provider/processor'
3
+
4
+ module RubySMB
5
+ class Server
6
+ module Share
7
+ module Provider
8
+ class Disk < Base
9
+ class Processor < Provider::Processor::Base
10
+ module Create
11
+ def do_nt_create_andx_smb1(request)
12
+ path = request.data_block.file_name.snapshot
13
+ path = path.encode.gsub(/\/|\\/, File::SEPARATOR)
14
+ path = path.delete_prefix(File::SEPARATOR)
15
+ local_path = get_local_path(path)
16
+ unless local_path && (local_path.file? || local_path.directory?)
17
+ logger.warn("Requested path does not exist: #{local_path}")
18
+ response = SMB1::Packet::EmptyPacket.new
19
+ response.smb_header.nt_status = WindowsError::NTStatus::STATUS_NO_SUCH_FILE
20
+ return response
21
+ end
22
+
23
+ response = SMB1::Packet::NtCreateAndxResponse.new
24
+ block = response.parameter_block
25
+ block.fid = rand(0xffff)
26
+ # fields are slightly different so #set_common_info can't be used :(
27
+ begin
28
+ block.create_time = local_path.birthtime
29
+ rescue NotImplementedError
30
+ logger.warn("The file system does not support #birthtime for #{path}")
31
+ end
32
+
33
+ block.last_access_time = local_path.atime
34
+ block.last_write_time = local_path.mtime
35
+ block.last_change_time = local_path.ctime
36
+ if local_path.file?
37
+ block.end_of_file = local_path.size
38
+ block.allocation_size = get_allocation_size(local_path)
39
+ end
40
+
41
+ @handles[response.parameter_block.fid] = Handle.new(path, local_path, false, local_path.file? ? local_path.open : nil)
42
+ response
43
+ end
44
+
45
+ def do_create_smb2(request)
46
+ unless request.create_disposition == RubySMB::Dispositions::FILE_OPEN
47
+ logger.warn("Can not handle CREATE request for disposition: #{request.create_disposition}")
48
+ raise NotImplementedError
49
+ end
50
+
51
+ # process the delayed io fields
52
+ request.name.read_now!
53
+ unless request.contexts_offset == 0
54
+ request.contexts.read_now!
55
+ request.contexts.each do |context|
56
+ context.name.read_now!
57
+ context.data.read_now!
58
+ end
59
+ end
60
+
61
+ path = request.name.snapshot
62
+ local_path = get_local_path(path)
63
+ unless local_path && (local_path.file? || local_path.directory?)
64
+ logger.warn("Requested path does not exist: #{local_path}")
65
+ response = RubySMB::SMB2::Packet::ErrorPacket.new
66
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND
67
+ return response
68
+ end
69
+
70
+ durable = false
71
+ response = RubySMB::SMB2::Packet::CreateResponse.new
72
+ response.create_action = RubySMB::CreateActions::FILE_OPENED
73
+ set_common_info(response, local_path)
74
+ response.file_id.persistent = Zlib::crc32(path)
75
+ response.file_id.volatile = rand(0xffffffff)
76
+
77
+ request.contexts.each do |req_ctx|
78
+ case req_ctx.name
79
+ when SMB2::CreateContext::CREATE_DURABLE_HANDLE
80
+ # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/9adbc354-5fad-40e7-9a62-4a4b6c1ff8a0
81
+ next if request.contexts.any? { |ctx| ctx.name == SMB2::CreateContext::CREATE_DURABLE_HANDLE_RECONNECT }
82
+
83
+ if request.contexts.any? { |ctx| [ SMB2::CreateContext::CREATE_DURABLE_HANDLE_V2, SMB2::CreateContext::CREATE_DURABLE_HANDLE_RECONNECT_v2 ].include?(ctx.name) }
84
+ response = RubySMB::SMB2::Packet::ErrorPacket.new
85
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER
86
+ return response
87
+ end
88
+
89
+ durable = true
90
+ res_ctx = SMB2::CreateContext::CreateDurableHandleResponse.new
91
+ when SMB2::CreateContext::CREATE_DURABLE_HANDLE_V2
92
+ # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/33e6800a-adf5-4221-af27-7e089b9e81d1
93
+ if request.contexts.any? { |ctx| [ SMB2::CreateContext::CREATE_DURABLE_HANDLE, SMB2::CreateContext::CREATE_DURABLE_HANDLE_RECONNECT, SMB2::CreateContext::CREATE_DURABLE_HANDLE_RECONNECT_v2 ].include?(ctx.name) }
94
+ response = RubySMB::SMB2::Packet::ErrorPacket.new
95
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER
96
+ return response
97
+ end
98
+
99
+ durable = true
100
+ res_ctx = SMB2::CreateContext::CreateDurableHandleV2Response.new(
101
+ timeout: 1000,
102
+ flags: req_ctx.data.flags
103
+ )
104
+ when SMB2::CreateContext::CREATE_QUERY_MAXIMAL_ACCESS
105
+ res_ctx = SMB2::CreateContext::CreateQueryMaximalAccessResponse.new(
106
+ maximal_access: maximal_access(path)
107
+ )
108
+ when SMB2::CreateContext::CREATE_QUERY_ON_DISK_ID
109
+ res_ctx = SMB2::CreateContext::CreateQueryOnDiskIdResponse.new(
110
+ disk_file_id: local_path.stat.ino,
111
+ volume_id: local_path.stat.dev
112
+ )
113
+ else
114
+ logger.warn("Can not handle CREATE context: #{req_ctx.name}")
115
+ next
116
+ end
117
+
118
+ response.contexts << SMB2::CreateContext::CreateContextResponse.new(name: res_ctx.class::NAME, data: res_ctx)
119
+ end
120
+
121
+ if response.contexts.length > 0
122
+ # fixup the offsets
123
+ response.contexts[0...-1].each do |ctx|
124
+ ctx.next_offset = ctx.num_bytes
125
+ end
126
+ response.contexts[-1].next_offset = 0
127
+ response.contexts_offset = response.buffer.abs_offset
128
+ response.contexts_length = response.buffer.num_bytes
129
+ else
130
+ response.contexts_offset = 0
131
+ response.contexts_length = 0
132
+ end
133
+
134
+ @handles[response.file_id.to_binary_s] = Handle.new(path, local_path, durable, local_path.file? ? local_path.open : nil)
135
+ response
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end