ruby_smb 3.0.0 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/examples/auth_capture.rb +28 -0
  4. data/examples/file_server.rb +76 -0
  5. data/lib/ruby_smb/create_actions.rb +21 -0
  6. data/lib/ruby_smb/field/nt_status.rb +20 -1
  7. data/lib/ruby_smb/fscc/file_information/file_ea_information.rb +14 -0
  8. data/lib/ruby_smb/fscc/file_information/file_network_open_information.rb +22 -0
  9. data/lib/ruby_smb/fscc/file_information/file_stream_information.rb +16 -0
  10. data/lib/ruby_smb/fscc/file_information.rb +29 -0
  11. data/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information.rb +46 -0
  12. data/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information.rb +19 -0
  13. data/lib/ruby_smb/fscc/file_system_information.rb +22 -0
  14. data/lib/ruby_smb/fscc.rb +1 -0
  15. data/lib/ruby_smb/generic_packet.rb +6 -0
  16. data/lib/ruby_smb/gss/provider/authenticator.rb +4 -0
  17. data/lib/ruby_smb/gss/provider/ntlm.rb +13 -3
  18. data/lib/ruby_smb/server/server_client/negotiation.rb +0 -2
  19. data/lib/ruby_smb/server/server_client/session_setup.rb +43 -32
  20. data/lib/ruby_smb/server/server_client/share_io.rb +28 -0
  21. data/lib/ruby_smb/server/server_client/tree_connect.rb +60 -0
  22. data/lib/ruby_smb/server/server_client.rb +214 -24
  23. data/lib/ruby_smb/server/session.rb +71 -0
  24. data/lib/ruby_smb/server/share/provider/disk.rb +437 -0
  25. data/lib/ruby_smb/server/share/provider/pipe.rb +27 -0
  26. data/lib/ruby_smb/server/share/provider/processor.rb +76 -0
  27. data/lib/ruby_smb/server/share/provider.rb +38 -0
  28. data/lib/ruby_smb/server/share.rb +11 -0
  29. data/lib/ruby_smb/server.rb +35 -3
  30. data/lib/ruby_smb/signing.rb +37 -11
  31. data/lib/ruby_smb/smb1/commands.rb +4 -0
  32. data/lib/ruby_smb/smb1.rb +0 -1
  33. data/lib/ruby_smb/smb2/bit_field/smb2_header_flags.rb +2 -1
  34. data/lib/ruby_smb/smb2/commands.rb +4 -0
  35. data/lib/ruby_smb/smb2/create_context/request.rb +64 -0
  36. data/lib/ruby_smb/smb2/create_context/response.rb +62 -0
  37. data/lib/ruby_smb/smb2/create_context.rb +74 -22
  38. data/lib/ruby_smb/smb2/packet/create_request.rb +44 -11
  39. data/lib/ruby_smb/smb2/packet/create_response.rb +17 -3
  40. data/lib/ruby_smb/smb2/packet/query_directory_request.rb +1 -1
  41. data/lib/ruby_smb/smb2/packet/query_directory_response.rb +2 -2
  42. data/lib/ruby_smb/smb2/packet/query_info_request.rb +43 -0
  43. data/lib/ruby_smb/smb2/packet/query_info_response.rb +23 -0
  44. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +1 -1
  45. data/lib/ruby_smb/smb2/packet/tree_disconnect_response.rb +1 -0
  46. data/lib/ruby_smb/smb2/packet.rb +2 -0
  47. data/lib/ruby_smb/smb2.rb +11 -0
  48. data/lib/ruby_smb/smb_error.rb +110 -0
  49. data/lib/ruby_smb/version.rb +1 -1
  50. data/lib/ruby_smb.rb +2 -0
  51. data/ruby_smb.gemspec +1 -1
  52. data/spec/lib/ruby_smb/field/nt_status_spec.rb +6 -2
  53. data/spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb +4 -0
  54. data/spec/lib/ruby_smb/server/server_client_spec.rb +36 -53
  55. data/spec/lib/ruby_smb/server/session_spec.rb +38 -0
  56. data/spec/lib/ruby_smb/server/share/provider/disk_spec.rb +61 -0
  57. data/spec/lib/ruby_smb/server/share/provider/pipe_spec.rb +31 -0
  58. data/spec/lib/ruby_smb/server/share/provider_spec.rb +13 -0
  59. data/spec/lib/ruby_smb/smb2/bit_field/header_flags_spec.rb +8 -2
  60. data/spec/lib/ruby_smb/smb2/{create_context_spec.rb → create_context/create_context_request_spec.rb} +1 -1
  61. data/spec/lib/ruby_smb/smb2/packet/create_request_spec.rb +5 -5
  62. data/spec/lib/ruby_smb/smb2/packet/create_response_spec.rb +9 -5
  63. data/spec/lib/ruby_smb/smb2/packet/query_directory_response_spec.rb +3 -2
  64. data.tar.gz.sig +0 -0
  65. metadata +35 -7
  66. metadata.gz.sig +0 -0
  67. 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