ruby_smb 3.0.0 → 3.0.4

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 (102) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/examples/anonymous_auth.rb +29 -6
  4. data/examples/auth_capture.rb +28 -0
  5. data/examples/file_server.rb +76 -0
  6. data/examples/read_file.rb +51 -10
  7. data/examples/tree_connect.rb +49 -8
  8. data/lib/ruby_smb/client/authentication.rb +11 -3
  9. data/lib/ruby_smb/client.rb +16 -2
  10. data/lib/ruby_smb/create_actions.rb +21 -0
  11. data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_encrypt_file_srv_request.rb +20 -0
  12. data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_encrypt_file_srv_response.rb +20 -0
  13. data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_open_file_raw_request.rb +21 -0
  14. data/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_open_file_raw_response.rb +21 -0
  15. data/lib/ruby_smb/dcerpc/encrypting_file_system.rb +44 -0
  16. data/lib/ruby_smb/dcerpc/print_system/rpc_add_printer_driver_ex_request.rb +22 -0
  17. data/lib/ruby_smb/dcerpc/print_system/rpc_add_printer_driver_ex_response.rb +20 -0
  18. data/lib/ruby_smb/dcerpc/print_system/rpc_enum_printer_drivers_request.rb +24 -0
  19. data/lib/ruby_smb/dcerpc/print_system/rpc_enum_printer_drivers_response.rb +23 -0
  20. data/lib/ruby_smb/dcerpc/print_system/rpc_get_printer_driver_directory_request.rb +24 -0
  21. data/lib/ruby_smb/dcerpc/print_system/rpc_get_printer_driver_directory_response.rb +22 -0
  22. data/lib/ruby_smb/dcerpc/print_system.rb +69 -0
  23. data/lib/ruby_smb/dcerpc.rb +2 -2
  24. data/lib/ruby_smb/field/nt_status.rb +20 -1
  25. data/lib/ruby_smb/fscc/file_information/file_ea_information.rb +14 -0
  26. data/lib/ruby_smb/fscc/file_information/file_network_open_information.rb +22 -0
  27. data/lib/ruby_smb/fscc/file_information/file_stream_information.rb +16 -0
  28. data/lib/ruby_smb/fscc/file_information.rb +29 -0
  29. data/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information.rb +46 -0
  30. data/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information.rb +19 -0
  31. data/lib/ruby_smb/fscc/file_system_information.rb +22 -0
  32. data/lib/ruby_smb/fscc.rb +1 -0
  33. data/lib/ruby_smb/generic_packet.rb +6 -0
  34. data/lib/ruby_smb/gss/provider/authenticator.rb +4 -0
  35. data/lib/ruby_smb/gss/provider/ntlm.rb +13 -3
  36. data/lib/ruby_smb/server/server_client/negotiation.rb +0 -2
  37. data/lib/ruby_smb/server/server_client/session_setup.rb +43 -32
  38. data/lib/ruby_smb/server/server_client/share_io.rb +28 -0
  39. data/lib/ruby_smb/server/server_client/tree_connect.rb +60 -0
  40. data/lib/ruby_smb/server/server_client.rb +214 -24
  41. data/lib/ruby_smb/server/session.rb +71 -0
  42. data/lib/ruby_smb/server/share/provider/disk.rb +437 -0
  43. data/lib/ruby_smb/server/share/provider/pipe.rb +27 -0
  44. data/lib/ruby_smb/server/share/provider/processor.rb +76 -0
  45. data/lib/ruby_smb/server/share/provider.rb +38 -0
  46. data/lib/ruby_smb/server/share.rb +11 -0
  47. data/lib/ruby_smb/server.rb +35 -3
  48. data/lib/ruby_smb/signing.rb +37 -11
  49. data/lib/ruby_smb/smb1/commands.rb +4 -0
  50. data/lib/ruby_smb/smb1/tree.rb +87 -79
  51. data/lib/ruby_smb/smb1.rb +0 -1
  52. data/lib/ruby_smb/smb2/bit_field/smb2_header_flags.rb +2 -1
  53. data/lib/ruby_smb/smb2/commands.rb +4 -0
  54. data/lib/ruby_smb/smb2/create_context/request.rb +64 -0
  55. data/lib/ruby_smb/smb2/create_context/response.rb +62 -0
  56. data/lib/ruby_smb/smb2/create_context.rb +74 -22
  57. data/lib/ruby_smb/smb2/packet/create_request.rb +44 -11
  58. data/lib/ruby_smb/smb2/packet/create_response.rb +17 -3
  59. data/lib/ruby_smb/smb2/packet/query_directory_request.rb +1 -1
  60. data/lib/ruby_smb/smb2/packet/query_directory_response.rb +2 -2
  61. data/lib/ruby_smb/smb2/packet/query_info_request.rb +43 -0
  62. data/lib/ruby_smb/smb2/packet/query_info_response.rb +23 -0
  63. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +1 -1
  64. data/lib/ruby_smb/smb2/packet/tree_disconnect_response.rb +1 -0
  65. data/lib/ruby_smb/smb2/packet.rb +2 -0
  66. data/lib/ruby_smb/smb2/tree.rb +80 -70
  67. data/lib/ruby_smb/smb2.rb +11 -0
  68. data/lib/ruby_smb/smb_error.rb +110 -0
  69. data/lib/ruby_smb/version.rb +1 -1
  70. data/lib/ruby_smb.rb +2 -0
  71. data/ruby_smb.gemspec +1 -1
  72. data/spec/lib/ruby_smb/client_spec.rb +10 -0
  73. data/spec/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_encrypt_file_srv_request_spec.rb +30 -0
  74. data/spec/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_encrypt_file_srv_response_spec.rb +30 -0
  75. data/spec/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_open_file_raw_request_spec.rb +38 -0
  76. data/spec/lib/ruby_smb/dcerpc/encrypting_file_system/efs_rpc_open_file_raw_response_spec.rb +38 -0
  77. data/spec/lib/ruby_smb/dcerpc/print_system/driver_container_spec.rb +41 -0
  78. data/spec/lib/ruby_smb/dcerpc/print_system/driver_info2_spec.rb +64 -0
  79. data/spec/lib/ruby_smb/dcerpc/print_system/rpc_add_printer_driver_ex_request_spec.rb +59 -0
  80. data/spec/lib/ruby_smb/dcerpc/print_system/rpc_add_printer_driver_ex_response_spec.rb +30 -0
  81. data/spec/lib/ruby_smb/dcerpc/print_system/rpc_enum_printer_drivers_request_spec.rb +62 -0
  82. data/spec/lib/ruby_smb/dcerpc/print_system/rpc_enum_printer_drivers_response_spec.rb +54 -0
  83. data/spec/lib/ruby_smb/dcerpc/print_system/rpc_get_printer_driver_directory_request_spec.rb +62 -0
  84. data/spec/lib/ruby_smb/dcerpc/print_system/rpc_get_printer_driver_directory_response_spec.rb +46 -0
  85. data/spec/lib/ruby_smb/field/nt_status_spec.rb +6 -2
  86. data/spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb +4 -0
  87. data/spec/lib/ruby_smb/server/server_client_spec.rb +36 -53
  88. data/spec/lib/ruby_smb/server/session_spec.rb +38 -0
  89. data/spec/lib/ruby_smb/server/share/provider/disk_spec.rb +61 -0
  90. data/spec/lib/ruby_smb/server/share/provider/pipe_spec.rb +31 -0
  91. data/spec/lib/ruby_smb/server/share/provider_spec.rb +13 -0
  92. data/spec/lib/ruby_smb/smb1/tree_spec.rb +3 -3
  93. data/spec/lib/ruby_smb/smb2/bit_field/header_flags_spec.rb +8 -2
  94. data/spec/lib/ruby_smb/smb2/{create_context_spec.rb → create_context/create_context_request_spec.rb} +1 -1
  95. data/spec/lib/ruby_smb/smb2/packet/create_request_spec.rb +5 -5
  96. data/spec/lib/ruby_smb/smb2/packet/create_response_spec.rb +9 -5
  97. data/spec/lib/ruby_smb/smb2/packet/query_directory_response_spec.rb +3 -2
  98. data/spec/lib/ruby_smb/smb2/tree_spec.rb +3 -3
  99. data.tar.gz.sig +0 -0
  100. metadata +71 -7
  101. metadata.gz.sig +0 -0
  102. data/lib/ruby_smb/smb1/create_actions.rb +0 -20
@@ -0,0 +1,437 @@
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
+ TYPE = TYPE_DISK
10
+ class Processor < Processor::Base
11
+ Handle = Struct.new(:remote_path, :local_path, :durable?)
12
+ def initialize(provider, server_client, session)
13
+ super
14
+ @handles = {}
15
+ @query_directory_context = {}
16
+ end
17
+
18
+ def maximal_access(path=nil)
19
+ RubySMB::SMB2::BitField::FileAccessMask.new(
20
+ read_attr: 1,
21
+ read_data: 1
22
+ )
23
+ end
24
+
25
+ def do_close_smb2(request)
26
+ local_path = get_local_path(request.file_id)
27
+ if local_path.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
+ @handles.delete(request.file_id.to_binary_s)
34
+ response = RubySMB::SMB2::Packet::CloseResponse.new
35
+ set_common_info(response, local_path)
36
+ response.flags = 1
37
+ response
38
+ end
39
+
40
+ def do_create_smb2(request)
41
+ unless request.create_disposition == RubySMB::Dispositions::FILE_OPEN
42
+ logger.warn("Can not handle CREATE request for disposition: #{request.create_disposition}")
43
+ raise NotImplementedError
44
+ end
45
+
46
+ # process the delayed io fields
47
+ request.name.read_now!
48
+ unless request.contexts_offset == 0
49
+ request.contexts.read_now!
50
+ request.contexts.each do |context|
51
+ context.name.read_now!
52
+ context.data.read_now!
53
+ end
54
+ end
55
+
56
+ path = request.name.snapshot
57
+ path = path.encode.gsub('\\', File::SEPARATOR)
58
+ local_path = get_local_path(path)
59
+ unless local_path && (local_path.file? || local_path.directory?)
60
+ logger.warn("Requested path does not exist: #{local_path}")
61
+ response = RubySMB::SMB2::Packet::ErrorPacket.new
62
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND
63
+ return response
64
+ end
65
+
66
+ durable = false
67
+ response = RubySMB::SMB2::Packet::CreateResponse.new
68
+ response.create_action = RubySMB::CreateActions::FILE_OPENED
69
+ set_common_info(response, local_path)
70
+ response.file_id.persistent = Zlib::crc32(path)
71
+ response.file_id.volatile = rand(0xffffffff)
72
+
73
+ request.contexts.each do |req_ctx|
74
+ case req_ctx.name
75
+ when SMB2::CreateContext::CREATE_DURABLE_HANDLE
76
+ # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/9adbc354-5fad-40e7-9a62-4a4b6c1ff8a0
77
+ next if request.contexts.any? { |ctx| ctx.name == SMB2::CreateContext::CREATE_DURABLE_HANDLE_RECONNECT }
78
+
79
+ if request.contexts.any? { |ctx| [ SMB2::CreateContext::CREATE_DURABLE_HANDLE_V2, SMB2::CreateContext::CREATE_DURABLE_HANDLE_RECONNECT_v2 ].include?(ctx.name) }
80
+ response = RubySMB::SMB2::Packet::ErrorPacket.new
81
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER
82
+ return response
83
+ end
84
+
85
+ durable = true
86
+ res_ctx = SMB2::CreateContext::CreateDurableHandleResponse.new
87
+ when SMB2::CreateContext::CREATE_DURABLE_HANDLE_V2
88
+ # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/33e6800a-adf5-4221-af27-7e089b9e81d1
89
+ 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) }
90
+ response = RubySMB::SMB2::Packet::ErrorPacket.new
91
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER
92
+ return response
93
+ end
94
+
95
+ durable = true
96
+ res_ctx = SMB2::CreateContext::CreateDurableHandleV2Response.new(
97
+ timeout: 1000,
98
+ flags: req_ctx.data.flags
99
+ )
100
+ when SMB2::CreateContext::CREATE_QUERY_MAXIMAL_ACCESS
101
+ res_ctx = SMB2::CreateContext::CreateQueryMaximalAccessResponse.new(
102
+ maximal_access: maximal_access(path)
103
+ )
104
+ when SMB2::CreateContext::CREATE_QUERY_ON_DISK_ID
105
+ res_ctx = SMB2::CreateContext::CreateQueryOnDiskIdResponse.new(
106
+ disk_file_id: local_path.stat.ino,
107
+ volume_id: local_path.stat.dev
108
+ )
109
+ else
110
+ logger.warn("Can not handle CREATE context: #{req_ctx.name}")
111
+ next
112
+ end
113
+
114
+ response.contexts << SMB2::CreateContext::CreateContextResponse.new(name: res_ctx.class::NAME, data: res_ctx)
115
+ end
116
+
117
+ if response.contexts.length > 0
118
+ # fixup the offsets
119
+ response.contexts[0...-1].each do |ctx|
120
+ ctx.next_offset = ctx.num_bytes
121
+ end
122
+ response.contexts[-1].next_offset = 0
123
+ response.contexts_offset = response.buffer.abs_offset
124
+ response.contexts_length = response.buffer.num_bytes
125
+ else
126
+ response.contexts_offset = 0
127
+ response.contexts_length = 0
128
+ end
129
+
130
+ @handles[response.file_id.to_binary_s] = Handle.new(path, local_path, durable)
131
+ response
132
+ end
133
+
134
+ def do_query_directory_smb2(request)
135
+ local_path = get_local_path(request.file_id)
136
+ if local_path.nil?
137
+ response = RubySMB::SMB2::Packet::ErrorPacket.new
138
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
139
+ return response
140
+ end
141
+
142
+ # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/29dfcc9b-3aec-406b-abb5-0b4fe96712e2
143
+ info_class = request.file_information_class.snapshot
144
+ begin
145
+ # probe #build_info to see if it supports the requested info class
146
+ build_info(Pathname.new(__FILE__), info_class)
147
+ rescue NotImplementedError
148
+ logger.warn("Can not handle QUERY_DIRECTORY request for class: #{info_class}")
149
+ raise
150
+ end
151
+
152
+ unless local_path.directory?
153
+ response = SMB2::Packet::ErrorPacket.new
154
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER
155
+ return response
156
+ end
157
+
158
+ search_pattern = request.name.snapshot.dup.encode
159
+ begin
160
+ search_regex = wildcard_to_regex(search_pattern)
161
+ rescue NotImplementedError
162
+ logger.warn("Can not handle QUERY_DIRECTORY wildcard pattern: #{search_pattern}")
163
+ raise
164
+ end
165
+
166
+ return_single = request.flags.return_single == 1
167
+
168
+ align = 8
169
+ infos = []
170
+ total_size = 0
171
+
172
+ if @query_directory_context[request.file_id.to_binary_s].nil? || request.flags.reopen == 1 || request.flags.restart_scans == 1
173
+ dirents = local_path.children.sort.to_a
174
+ dirents.unshift(local_path.parent) unless local_path.parent == local_path
175
+ dirents.unshift(local_path)
176
+ @query_directory_context[request.file_id.to_binary_s] = dirents
177
+ else
178
+ dirents = @query_directory_context[request.file_id.to_binary_s]
179
+ end
180
+
181
+ while dirents.length > 0
182
+ dirent = dirents.shift
183
+ next unless dirent.file? || dirent.directory? # filter out everything but files and directories
184
+
185
+ case dirent
186
+ when local_path
187
+ dirent_name = '.'
188
+ when local_path.parent
189
+ dirent_name = '..'
190
+ else
191
+ dirent_name = dirent.basename.to_s
192
+ end
193
+ next unless search_regex.match?(dirent_name)
194
+
195
+ info = build_info(dirent, info_class, rename: dirent_name)
196
+ info_size = info.num_bytes + ((align - info.num_bytes % align) % align)
197
+ if total_size + info_size > request.output_length
198
+ dirents.unshift(dirent) # no space left for this one so put it back
199
+ break
200
+ end
201
+
202
+ infos << info
203
+ total_size += info_size
204
+ break if return_single
205
+ end
206
+
207
+ if infos.length == 0
208
+ response = SMB2::Packet::QueryDirectoryResponse.new
209
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NO_MORE_FILES
210
+ return response
211
+ end
212
+
213
+ response = SMB2::Packet::QueryDirectoryResponse.new
214
+ infos.last.next_offset = 0 if infos.last
215
+ buffer = ""
216
+ infos.each do |info|
217
+ info = info.to_binary_s
218
+ buffer << info + "\x00".b * ((align - info.length % align) % align)
219
+ end
220
+ response.buffer = buffer
221
+ response
222
+ end
223
+
224
+ def do_query_info_smb2(request)
225
+ local_path = get_local_path(request.file_id)
226
+ if local_path.nil?
227
+ response = RubySMB::SMB2::Packet::ErrorPacket.new
228
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
229
+ return response
230
+ end
231
+
232
+ case request.info_type
233
+ when SMB2::SMB2_INFO_FILE
234
+ info = query_info_smb2_file(request, local_path)
235
+ when SMB2::SMB2_INFO_FILESYSTEM
236
+ info = query_info_smb2_filesystem(request, local_path)
237
+ else
238
+ logger.warn("Can not handle QUERY_INFO request for type: #{request.info_type}, class: #{request.file_information_class}")
239
+ raise NotImplementedError
240
+ end
241
+
242
+ response = SMB2::Packet::QueryInfoResponse.new
243
+ response.buffer = info.to_binary_s
244
+ response
245
+ end
246
+
247
+ def do_read_smb2(request)
248
+ local_path = get_local_path(request.file_id)
249
+ if local_path.nil?
250
+ response = RubySMB::SMB2::Packet::ErrorPacket.new
251
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
252
+ return response
253
+ end
254
+
255
+ raise NotImplementedError unless request.channel == SMB2::SMB2_CHANNEL_NONE
256
+
257
+ buffer = nil
258
+ local_path.open do |file|
259
+ file.seek(request.offset.snapshot)
260
+ buffer = file.read(request.read_length)
261
+ end
262
+
263
+ # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/21e8b343-34e9-4fca-8d93-03dd2d3e961e
264
+ if buffer.nil? || buffer.length == 0 || buffer.length < request.min_bytes
265
+ response = SMB2::Packet::ErrorPacket.new
266
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_END_OF_FILE
267
+ return response
268
+ end
269
+
270
+ response = SMB2::Packet::ReadResponse.new
271
+ response.data_length = buffer.length
272
+ response.buffer = buffer
273
+ response
274
+ end
275
+
276
+ private
277
+
278
+ def build_file_attributes(path)
279
+ file_attributes = Fscc::FileAttributes.new
280
+ if path.file?
281
+ file_attributes.normal = 1
282
+ elsif path.directory?
283
+ file_attributes.directory = 1
284
+ end
285
+ file_attributes
286
+ end
287
+
288
+ def build_info(path, info_class, rename: nil)
289
+ case info_class
290
+ when Fscc::FileInformation::FILE_ID_BOTH_DIRECTORY_INFORMATION
291
+ info = Fscc::FileInformation::FileIdBothDirectoryInformation.new
292
+ set_common_info(info, path)
293
+ info.file_name = rename || path.basename.to_s
294
+ when Fscc::FileInformation::FILE_FULL_DIRECTORY_INFORMATION
295
+ info = Fscc::FileInformation::FileFullDirectoryInformation.new
296
+ set_common_info(info, path)
297
+ info.file_name = rename || path.basename.to_s
298
+ else
299
+ raise NotImplementedError, "unsupported info class: #{info_class}"
300
+ end
301
+
302
+ align = 8
303
+ info.next_offset = info.num_bytes + ((align - info.num_bytes % align) % align)
304
+ info
305
+ end
306
+
307
+ def get_allocation_size(path)
308
+ (path.size + (4095 - (path.size + 4095) % 4096))
309
+ end
310
+
311
+ def get_local_path(path)
312
+ case path
313
+ when Field::Smb2Fileid
314
+ local_path = @handles[path.to_binary_s]&.local_path
315
+ when ::String
316
+ local_path = (provider.path + path.encode).cleanpath
317
+ # TODO: report / handle directory traversal issues more robustly
318
+ raise RuntimeError unless local_path == provider.path || local_path.to_s.start_with?(provider.path.to_s + '/')
319
+ else
320
+ raise NotImplementedError, "Can not get the local path for: #{path.inspect}"
321
+ end
322
+
323
+ local_path
324
+ end
325
+
326
+ def query_info_smb2_file(request, local_path)
327
+ raise ArgumentError unless request.info_type == SMB2::SMB2_INFO_FILE
328
+
329
+ case request.file_information_class
330
+ when Fscc::FileInformation::FILE_EA_INFORMATION
331
+ info = Fscc::FileInformation::FileEaInformation.new
332
+ when Fscc::FileInformation::FILE_NETWORK_OPEN_INFORMATION
333
+ info = Fscc::FileInformation::FileNetworkOpenInformation.new
334
+ set_common_info(info, local_path)
335
+ when Fscc::FileInformation::FILE_NORMALIZED_NAME_INFORMATION
336
+ info = Fscc::FileInformation::FileNameInformation.new(file_name: @handles[request.file_id.to_binary_s].remote_path)
337
+ when Fscc::FileInformation::FILE_STREAM_INFORMATION
338
+ raise NotImplementedError unless local_path.file?
339
+
340
+ info = Fscc::FileInformation::FileStreamInformation.new(
341
+ stream_size: local_path.size,
342
+ stream_allocation_size: get_allocation_size(local_path),
343
+ stream_name: '::$DATA'
344
+ )
345
+ else
346
+ logger.warn("Can not handle QUERY_INFO request for type: #{request.info_type}, class: #{request.file_information_class}")
347
+ raise NotImplementedError
348
+ end
349
+
350
+ info
351
+ end
352
+
353
+ def query_info_smb2_filesystem(request, local_path)
354
+ raise ArgumentError unless request.info_type == SMB2::SMB2_INFO_FILESYSTEM
355
+
356
+ case request.file_information_class
357
+ when Fscc::FileSystemInformation::FILE_FS_ATTRIBUTE_INFORMATION
358
+ # emulate NTFS just like Samba does
359
+ info = Fscc::FileSystemInformation::FileFsAttributeInformation.new(
360
+ file_system_attributes: {
361
+ file_case_sensitive_search: 1,
362
+ file_case_preserved_names: 1,
363
+ file_unicode_on_disk: 1,
364
+ file_supports_object_ids: 1,
365
+ },
366
+ maximum_component_name_length: 255,
367
+ file_system_name: 'NTFS'
368
+ )
369
+ when Fscc::FileSystemInformation::FILE_FS_VOLUME_INFORMATION
370
+ info = Fscc::FileSystemInformation::FileFsVolumeInformation.new(
371
+ volume_serial_number: provider.path.stat.ino,
372
+ volume_label: provider.name
373
+ )
374
+ else
375
+ logger.warn("Can not handle QUERY_INFO request for type: #{request.info_type}, class: #{request.file_information_class}")
376
+ raise NotImplementedError
377
+ end
378
+
379
+ info
380
+ end
381
+
382
+ # A bunch of structures have these common fields with the same meaning, so set them all here
383
+ def set_common_info(info, path)
384
+ begin
385
+ info.create_time = path.birthtime
386
+ rescue NotImplementedError
387
+ logger.warn("The file system does not support #birthtime for #{path}")
388
+ end
389
+
390
+ info.last_access = path.atime
391
+ info.last_write = path.mtime
392
+ info.last_change = path.ctime
393
+ if path.file?
394
+ info.end_of_file = path.size
395
+ info.allocation_size = get_allocation_size(path)
396
+ end
397
+ info.file_attributes = build_file_attributes(path)
398
+ end
399
+
400
+ # Turn a wildcard expression into a regex. Not all wildcard
401
+ # characters are supported. Wildcards that can not be converted will
402
+ # raise a NotImplementedError.
403
+ #
404
+ # @param [String] wildcard The wildcard expression to convert.
405
+ # @return [Regexp] The converted expression.
406
+ # @raise [NotImplementedError] Raised when the wildcard can not be
407
+ # converted.
408
+ def wildcard_to_regex(wildcard)
409
+ return Regexp.new('.*') if ['*.*', ''].include?(wildcard)
410
+
411
+ if wildcard.each_char.any? { |c| c == '<' || c == '>' }
412
+ # the < > wildcard operators are not supported
413
+ raise NotImplementedError
414
+ end
415
+
416
+ wildcard = Regexp.escape(wildcard)
417
+ wildcard = wildcard.gsub(/(\\\?)+$/) { |match| ".{0,#{match.length / 2}}"}
418
+ wildcard = wildcard.gsub('\?', '.')
419
+ wildcard = wildcard.gsub('\*', '.*')
420
+ wildcard = wildcard.gsub('"', '\.')
421
+ Regexp.new('^' + wildcard + '$')
422
+ end
423
+ end
424
+
425
+ def initialize(name, path)
426
+ path = Pathname.new(File.expand_path(path))
427
+ raise ArgumentError unless path.directory?
428
+ @path = path
429
+ super(name)
430
+ end
431
+
432
+ attr_accessor :path
433
+ end
434
+ end
435
+ end
436
+ end
437
+ end
@@ -0,0 +1,27 @@
1
+ require 'ruby_smb/server/share/provider/processor'
2
+
3
+ module RubySMB
4
+ class Server
5
+ module Share
6
+ module Provider
7
+ class Pipe < Base
8
+ TYPE = TYPE_PIPE
9
+ class Processor < Processor::Base
10
+ end
11
+ end
12
+
13
+ class IpcPipe < Pipe
14
+ class Processor < Processor::Base
15
+ def maximal_access(path=nil)
16
+ RubySMB::SMB2::BitField::DirectoryAccessMask.read([0x001f00a9].pack('V'))
17
+ end
18
+ end
19
+
20
+ def initialize(name='IPC$')
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,76 @@
1
+ module RubySMB
2
+ class Server
3
+ module Share
4
+ module Provider
5
+ module Processor
6
+ # A processor is unique to a particular client connection-session
7
+ # combination and provides the share's functionality.
8
+ class Base
9
+ def initialize(provider, server_client, session)
10
+ @provider = provider
11
+ @server_client = server_client
12
+ @session = session
13
+ end
14
+
15
+ # Get the maximum access that can be obtained for the specified
16
+ # path. If no path is specified, the maximum access for the share as
17
+ # a whole is returned.
18
+ #
19
+ # @param [Pathname] path
20
+ # @return [RubySMB::SMB2::BitField::FileAccessMask]
21
+ def maximal_access(path=nil)
22
+ RubySMB::SMB2::BitField::FileAccessMask.new
23
+ end
24
+
25
+ def disconnect!
26
+ end
27
+
28
+ def do_close_smb2(request)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ def do_create_smb2(request)
33
+ raise NotImplementedError
34
+ end
35
+
36
+ def do_ioctl_smb2(request)
37
+ response = RubySMB::SMB2::Packet::IoctlResponse.new
38
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_FOUND
39
+ response.smb2_header.credits = 1
40
+ response
41
+ end
42
+
43
+ def do_query_directory_smb2(request)
44
+ raise NotImplementedError
45
+ end
46
+
47
+ def do_query_info_smb2(request)
48
+ raise NotImplementedError
49
+ end
50
+
51
+ def do_read_smb2(request)
52
+ raise NotImplementedError
53
+ end
54
+
55
+ #
56
+ # The logger object associated with this instance.
57
+ #
58
+ # @return [Logger]
59
+ def logger
60
+ @server_client.logger
61
+ end
62
+
63
+ def server
64
+ @server_client.server
65
+ end
66
+
67
+ # The underlying share provider that this is a processor for.
68
+ # @!attribute [r] provider
69
+ # @return [RubySMB::Server::Share::Provider::Base]
70
+ attr_accessor :provider
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,38 @@
1
+ module RubySMB
2
+ class Server
3
+ module Share
4
+ module Provider
5
+ # The share provider defines the share and its attributes such as its
6
+ # type and name. It is shared across all client connections and
7
+ # sessions.
8
+ class Base
9
+ # @param [String] name The name of this share.
10
+ def initialize(name)
11
+ @name = name
12
+ end
13
+
14
+ # Create a new, session-specific processor instance for this share.
15
+ #
16
+ # @param [RubySMB::Server::ServerClient] server_client The client connection.
17
+ # @param [RubySMB::Server::Session] session The session object.
18
+ def new_processor(server_client, session)
19
+ self.class::Processor.new(self, server_client, session)
20
+ end
21
+
22
+ # The type of this share.
23
+ def type
24
+ self.class::TYPE
25
+ end
26
+
27
+ # The name of this share.
28
+ # @!attribute [r] name
29
+ # @return [String]
30
+ attr_accessor :name
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ require 'ruby_smb/server/share/provider/disk'
38
+ require 'ruby_smb/server/share/provider/pipe'
@@ -0,0 +1,11 @@
1
+ module RubySMB
2
+ class Server
3
+ module Share
4
+ TYPE_DISK = :disk
5
+ TYPE_PIPE = :pipe
6
+ TYPE_PRINT = :print
7
+ end
8
+ end
9
+ end
10
+
11
+ require 'ruby_smb/server/share/provider'
@@ -1,3 +1,4 @@
1
+ require 'logger'
1
2
  require 'socket'
2
3
 
3
4
  module RubySMB
@@ -6,13 +7,15 @@ module RubySMB
6
7
  # available at this time. The negotiating and authentication is supported for SMB versions 1 through 3.1.1.
7
8
  class Server
8
9
  require 'ruby_smb/server/server_client'
10
+ require 'ruby_smb/server/session'
11
+ require 'ruby_smb/server/share'
9
12
  require 'ruby_smb/gss/provider/ntlm'
10
13
 
11
14
  Connection = Struct.new(:client, :thread)
12
15
 
13
16
  # @param server_sock the socket on which the server should listen
14
17
  # @param [Gss::Provider] the authentication provider
15
- def initialize(server_sock: nil, gss_provider: nil)
18
+ def initialize(server_sock: nil, gss_provider: nil, logger: nil)
16
19
  server_sock = ::TCPServer.new(445) if server_sock.nil?
17
20
 
18
21
  @guid = Random.new.bytes(16)
@@ -21,6 +24,27 @@ module RubySMB
21
24
  @gss_provider = gss_provider || Gss::Provider::NTLM.new
22
25
  # reject the wildcard dialect because it's not a real dialect we can use for this purpose
23
26
  @dialects = RubySMB::Dialect::ALL.keys.reject { |dialect| dialect == "0x%04x" % RubySMB::SMB2::SMB2_WILDCARD_REVISION }.reverse
27
+
28
+ case logger
29
+ when nil
30
+ @logger = Logger.new(File.open(File::NULL, 'w'))
31
+ when :stdout
32
+ @logger = Logger.new(STDOUT)
33
+ when :stderr
34
+ @logger = Logger.new(STDERR)
35
+ else
36
+ @logger = logger
37
+ end
38
+
39
+ # share name => provider instance
40
+ @shares = {
41
+ 'IPC$' => Share::Provider::IpcPipe.new
42
+ }
43
+ end
44
+
45
+ def add_share(share_provider)
46
+ logger.debug("Adding #{share_provider.type} share: #{share_provider.name}")
47
+ @shares[share_provider.name] = share_provider
24
48
  end
25
49
 
26
50
  # Run the server and accept any connections. For each connection, the block will be executed if specified. When the
@@ -28,7 +52,7 @@ module RubySMB
28
52
  def run(&block)
29
53
  loop do
30
54
  sock = @socket.accept
31
- server_client = ServerClient.new(self, RubySMB::Dispatcher::Socket.new(sock))
55
+ server_client = ServerClient.new(self, RubySMB::Dispatcher::Socket.new(sock, read_timeout: nil))
32
56
  @connections << Connection.new(server_client, Thread.new { server_client.run })
33
57
 
34
58
  break unless block.nil? || block.call(server_client)
@@ -36,7 +60,7 @@ module RubySMB
36
60
  end
37
61
 
38
62
  # The dialects that this server will negotiate with clients, in ascending order of preference.
39
- # @!attribute [r] dialects
63
+ # @!attribute [rw] dialects
40
64
  # @return [Array<String>]
41
65
  attr_accessor :dialects
42
66
 
@@ -49,6 +73,14 @@ module RubySMB
49
73
  # The 16 byte GUID that uniquely identifies this server instance.
50
74
  # @!attribute [r] guid
51
75
  attr_reader :guid
76
+
77
+ # The logger instance to use for diagnostic messages.
78
+ # @!attribute [r] logger
79
+ attr_reader :logger
80
+
81
+ # The shares that are provided by this server
82
+ # @!attribute [r] shares
83
+ attr_reader :shares
52
84
  end
53
85
  end
54
86