ruby_smb 3.1.1 → 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 (77) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -2
  3. data/examples/file_server.rb +8 -1
  4. data/examples/virtual_file_server.rb +143 -0
  5. data/lib/ruby_smb/fscc/file_information/file_access_information.rb +15 -0
  6. data/lib/ruby_smb/fscc/file_information/file_alignment_information.rb +45 -0
  7. data/lib/ruby_smb/fscc/file_information/file_all_information.rb +23 -0
  8. data/lib/ruby_smb/fscc/file_information/file_basic_information.rb +20 -0
  9. data/lib/ruby_smb/fscc/file_information/file_both_directory_information.rb +3 -3
  10. data/lib/ruby_smb/fscc/file_information/file_directory_information.rb +3 -3
  11. data/lib/ruby_smb/fscc/file_information/file_ea_information.rb +1 -0
  12. data/lib/ruby_smb/fscc/file_information/file_full_directory_information.rb +3 -3
  13. data/lib/ruby_smb/fscc/file_information/file_id_both_directory_information.rb +3 -3
  14. data/lib/ruby_smb/fscc/file_information/file_id_full_directory_information.rb +3 -3
  15. data/lib/ruby_smb/fscc/file_information/file_internal_information.rb +15 -0
  16. data/lib/ruby_smb/fscc/file_information/file_mode_information.rb +29 -0
  17. data/lib/ruby_smb/fscc/file_information/file_name_information.rb +16 -0
  18. data/lib/ruby_smb/fscc/file_information/file_names_information.rb +1 -1
  19. data/lib/ruby_smb/fscc/file_information/file_normalized_name_information.rb +16 -0
  20. data/lib/ruby_smb/fscc/file_information/file_position_information.rb +15 -0
  21. data/lib/ruby_smb/fscc/file_information/file_rename_information.rb +1 -1
  22. data/lib/ruby_smb/fscc/file_information/file_standard_information.rb +20 -0
  23. data/lib/ruby_smb/fscc/file_information/file_stream_information.rb +3 -0
  24. data/lib/ruby_smb/fscc/file_information.rb +41 -8
  25. data/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information.rb +1 -0
  26. data/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information.rb +1 -0
  27. data/lib/ruby_smb/fscc/file_system_information.rb +4 -0
  28. data/lib/ruby_smb/gss/provider/ntlm.rb +16 -3
  29. data/lib/ruby_smb/gss/provider.rb +10 -1
  30. data/lib/ruby_smb/server/server_client/session_setup.rb +4 -2
  31. data/lib/ruby_smb/server/server_client.rb +9 -1
  32. data/lib/ruby_smb/server/session.rb +5 -1
  33. data/lib/ruby_smb/server/share/provider/disk/processor/close.rb +9 -5
  34. data/lib/ruby_smb/server/share/provider/disk/processor/create.rb +2 -2
  35. data/lib/ruby_smb/server/share/provider/disk/processor/query.rb +2 -2
  36. data/lib/ruby_smb/server/share/provider/disk/processor/read.rb +15 -14
  37. data/lib/ruby_smb/server/share/provider/disk/processor.rb +76 -12
  38. data/lib/ruby_smb/server/share/provider/disk.rb +8 -2
  39. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_file.rb +85 -0
  40. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname.rb +196 -0
  41. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat.rb +175 -0
  42. data/lib/ruby_smb/server/share/provider/virtual_disk.rb +116 -0
  43. data/lib/ruby_smb/server/share/provider.rb +1 -0
  44. data/lib/ruby_smb/server.rb +13 -3
  45. data/lib/ruby_smb/smb2/tree.rb +1 -0
  46. data/lib/ruby_smb/version.rb +1 -1
  47. data/spec/lib/ruby_smb/fscc/file_information/file_access_information_spec.rb +21 -0
  48. data/spec/lib/ruby_smb/fscc/file_information/file_alignment_information_spec.rb +21 -0
  49. data/spec/lib/ruby_smb/fscc/file_information/file_all_information_spec.rb +61 -0
  50. data/spec/lib/ruby_smb/fscc/file_information/file_basic_information_spec.rb +41 -0
  51. data/spec/lib/ruby_smb/fscc/file_information/file_both_directory_information_spec.rb +59 -10
  52. data/spec/lib/ruby_smb/fscc/file_information/file_directory_information_spec.rb +30 -12
  53. data/spec/lib/ruby_smb/fscc/file_information/file_ea_information_spec.rb +21 -0
  54. data/spec/lib/ruby_smb/fscc/file_information/file_full_directory_information_spec.rb +30 -12
  55. data/spec/lib/ruby_smb/fscc/file_information/file_id_both_directory_information_spec.rb +63 -10
  56. data/spec/lib/ruby_smb/fscc/file_information/file_id_full_directory_information_spec.rb +30 -12
  57. data/spec/lib/ruby_smb/fscc/file_information/file_internal_information_spec.rb +21 -0
  58. data/spec/lib/ruby_smb/fscc/file_information/file_mode_information_spec.rb +21 -0
  59. data/spec/lib/ruby_smb/fscc/file_information/file_name_information_spec.rb +44 -0
  60. data/spec/lib/ruby_smb/fscc/file_information/file_names_information_spec.rb +30 -12
  61. data/spec/lib/ruby_smb/fscc/file_information/file_network_open_information_spec.rb +51 -0
  62. data/spec/lib/ruby_smb/fscc/file_information/file_normalized_name_information_spec.rb +44 -0
  63. data/spec/lib/ruby_smb/fscc/file_information/file_position_information_spec.rb +21 -0
  64. data/spec/lib/ruby_smb/fscc/file_information/file_rename_information_spec.rb +1 -1
  65. data/spec/lib/ruby_smb/fscc/file_information/file_standard_information_spec.rb +41 -0
  66. data/spec/lib/ruby_smb/fscc/file_information/file_stream_information_spec.rb +51 -0
  67. data/spec/lib/ruby_smb/fscc/file_information_spec.rb +14 -0
  68. data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information_spec.rb +46 -0
  69. data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information_spec.rb +51 -0
  70. data/spec/lib/ruby_smb/fscc/file_system_information_spec.rb +14 -0
  71. data/spec/lib/ruby_smb/server/server_client_spec.rb +15 -0
  72. data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname_spec.rb +581 -0
  73. data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat_spec.rb +207 -0
  74. data/spec/lib/ruby_smb/server/share/provider/virtual_disk_spec.rb +122 -0
  75. data.tar.gz.sig +0 -0
  76. metadata +57 -2
  77. metadata.gz.sig +0 -0
@@ -17,10 +17,26 @@ module RubySMB
17
17
  # contents of a directory.
18
18
  FILE_BOTH_DIRECTORY_INFORMATION = 0x03
19
19
 
20
+ # Information class used to query or set file information.
21
+ FILE_BASIC_INFORMATION = 0x04
22
+
23
+ # Information class is used to query file information.
24
+ FILE_STANDARD_INFORMATION = 0x05
25
+
26
+ # Information class used to query for the file system's 64-bit file ID.
27
+ FILE_INTERNAL_INFORMATION = 0x06
28
+
20
29
  # Information class used to query for the size of the extended attributes
21
30
  # (EA) for a file.
22
31
  FILE_EA_INFORMATION = 0x07
23
32
 
33
+ # Information class used to query the access rights of a file that were
34
+ # granted when the file was opened.
35
+ FILE_ACCESS_INFORMATION = 0x08
36
+
37
+ # Information class is used locally to query the name of a file.
38
+ FILE_NAME_INFORMATION = 0x09
39
+
24
40
  # Information class used to rename a file.
25
41
  FILE_RENAME_INFORMATION = 0x0A
26
42
 
@@ -31,6 +47,17 @@ module RubySMB
31
47
  # Information class used to mark a file for deletion.
32
48
  FILE_DISPOSITION_INFORMATION = 0x0D
33
49
 
50
+ # Information class used to query or set the position of the file pointer
51
+ # within a file.
52
+ FILE_POSITION_INFORMATION = 0x0E
53
+
54
+ # Information class used to query or set the mode of the file.
55
+ FILE_MODE_INFORMATION = 0x10
56
+
57
+ # Information class used to query the buffer alignment required by the
58
+ # underlying device.
59
+ FILE_ALIGNMENT_INFORMATION = 0x11
60
+
34
61
  # Information class used to enumerate the data streams of a file or a
35
62
  # directory.
36
63
  FILE_STREAM_INFORMATION = 0x16
@@ -57,6 +84,10 @@ module RubySMB
57
84
  FILE_NORMALIZED_NAME_INFORMATION = 0x30
58
85
 
59
86
 
87
+ # Information class is used to query a collection of file information
88
+ # structures.
89
+ FILE_ALL_INFORMATION = 0x12
90
+
60
91
  # These Information Classes can be used by SMB1 using the pass-through
61
92
  # Information Levels when available on the server (CAP_INFOLEVEL_PASSTHRU
62
93
  # capability flag in an SMB_COM_NEGOTIATE server response). The constant
@@ -69,25 +100,27 @@ module RubySMB
69
100
  constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value }
70
101
  end
71
102
 
72
- # The FILE_NAME_INFORMATION type as defined in
73
- # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/20406fb1-605f-4629-ba9a-c67ee25f23d2
74
- class FileNameInformation < BinData::Record
75
- endian :little
76
- uint32 :file_name_length, label: 'File Name Length', initial_value: -> { file_name.do_num_bytes }
77
- string16 :file_name, label: 'File Name', read_length: -> { file_name_length }
78
- end
79
-
80
103
  require 'ruby_smb/fscc/file_information/file_directory_information'
81
104
  require 'ruby_smb/fscc/file_information/file_full_directory_information'
82
105
  require 'ruby_smb/fscc/file_information/file_disposition_information'
83
106
  require 'ruby_smb/fscc/file_information/file_id_full_directory_information'
84
107
  require 'ruby_smb/fscc/file_information/file_both_directory_information'
85
108
  require 'ruby_smb/fscc/file_information/file_id_both_directory_information'
109
+ require 'ruby_smb/fscc/file_information/file_name_information'
86
110
  require 'ruby_smb/fscc/file_information/file_names_information'
111
+ require 'ruby_smb/fscc/file_information/file_normalized_name_information'
87
112
  require 'ruby_smb/fscc/file_information/file_rename_information'
88
113
  require 'ruby_smb/fscc/file_information/file_network_open_information'
89
114
  require 'ruby_smb/fscc/file_information/file_ea_information'
90
115
  require 'ruby_smb/fscc/file_information/file_stream_information'
116
+ require 'ruby_smb/fscc/file_information/file_basic_information'
117
+ require 'ruby_smb/fscc/file_information/file_standard_information'
118
+ require 'ruby_smb/fscc/file_information/file_internal_information'
119
+ require 'ruby_smb/fscc/file_information/file_access_information'
120
+ require 'ruby_smb/fscc/file_information/file_position_information'
121
+ require 'ruby_smb/fscc/file_information/file_mode_information'
122
+ require 'ruby_smb/fscc/file_information/file_alignment_information'
123
+ require 'ruby_smb/fscc/file_information/file_all_information'
91
124
  end
92
125
  end
93
126
  end
@@ -7,6 +7,7 @@ module RubySMB
7
7
  CLASS_LEVEL = FileSystemInformation::FILE_FS_ATTRIBUTE_INFORMATION
8
8
 
9
9
  endian :little
10
+
10
11
  struct :file_system_attributes, label: 'File System Attributes' do
11
12
  bit1 :file_supports_reparse_points, label: 'FS Supports Reparse Points'
12
13
  bit1 :file_supports_sparse_files, label: 'FS Supports Sparse Files'
@@ -7,6 +7,7 @@ module RubySMB
7
7
  CLASS_LEVEL = FileSystemInformation::FILE_FS_VOLUME_INFORMATION
8
8
 
9
9
  endian :little
10
+
10
11
  file_time :volume_creation_time, label: 'Volume Creation Time'
11
12
  uint32 :volume_serial_number, label: 'Volume Serial Number'
12
13
  uint32 :volume_label_length, label: 'Volume Label Length', initial_value: -> { volume_label.do_num_bytes }
@@ -15,6 +15,10 @@ module RubySMB
15
15
  FILE_FS_VOLUME_FLAGS_INFORMATION = 10
16
16
  FILE_FS_SECTOR_SIZE_INFORMATION = 11
17
17
 
18
+ def self.name(value)
19
+ constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value }
20
+ end
21
+
18
22
  require 'ruby_smb/fscc/file_system_information/file_fs_attribute_information'
19
23
  require 'ruby_smb/fscc/file_system_information/file_fs_volume_information'
20
24
  end
@@ -113,6 +113,7 @@ module RubySMB
113
113
  def process_ntlm_type3(type3_msg)
114
114
  if type3_msg.user == '' && type3_msg.domain == ''
115
115
  if @provider.allow_anonymous
116
+ @session_key = "\x00".b * 16 # see MS-NLMP section 3.4
116
117
  return WindowsError::NTStatus::STATUS_SUCCESS
117
118
  end
118
119
 
@@ -126,6 +127,12 @@ module RubySMB
126
127
  domain: type3_msg.domain
127
128
  )
128
129
  if account.nil?
130
+ if @provider.allow_guests
131
+ logger.info("NTLM authentication request succeeded for #{dbg_string} (guest)")
132
+ @session_key = "\x00".b * 16 # see MS-NLMP section 3.4
133
+ return WindowsError::NTStatus::STATUS_SUCCESS
134
+ end
135
+
129
136
  logger.info("NTLM authentication request failed for #{dbg_string} (no account)")
130
137
  return WindowsError::NTStatus::STATUS_LOGON_FAILURE
131
138
  end
@@ -233,25 +240,31 @@ module RubySMB
233
240
  domain: type3_msg.domain
234
241
  )
235
242
  if account.nil?
236
- if @provider.allow_anonymous
243
+ if type3_msg.user == ''
244
+ is_guest = false
237
245
  identity = IDENTITY_ANONYMOUS
246
+ else
247
+ is_guest = true
248
+ identity = Account.new(type3_msg.user.encode(''.encoding), '', type3_msg.domain.encode(''.encoding)).to_s
238
249
  end
239
250
  else
251
+ is_guest = false
240
252
  identity = account.to_s
241
253
  end
242
254
  end
243
255
 
244
- Result.new(buffer, nt_status, identity)
256
+ Result.new(buffer, nt_status, identity, is_guest)
245
257
  end
246
258
  end
247
259
 
248
260
  # @param [Boolean] allow_anonymous whether or not to allow anonymous authentication attempts
249
261
  # @param [String] default_domain the default domain to use for authentication, unless specified 'WORKGROUP' will
250
262
  # be used
251
- def initialize(allow_anonymous: false, default_domain: 'WORKGROUP')
263
+ def initialize(allow_anonymous: false, allow_guests: false, default_domain: 'WORKGROUP')
252
264
  raise ArgumentError, 'Must specify a default domain' unless default_domain
253
265
 
254
266
  @allow_anonymous = allow_anonymous
267
+ @allow_guests = allow_guests
255
268
  @default_domain = default_domain
256
269
  @accounts = []
257
270
  @generate_server_challenge = -> { SecureRandom.bytes(8) }
@@ -7,7 +7,11 @@ module RubySMB
7
7
  # A special constant implying that the authenticated user is anonymous.
8
8
  IDENTITY_ANONYMOUS = :anonymous
9
9
  # The result of a processed GSS request.
10
- Result = Struct.new(:buffer, :nt_status, :identity)
10
+ Result = Struct.new(:buffer, :nt_status, :identity, :is_guest) do
11
+ def is_anonymous
12
+ identity == Gss::Provider::IDENTITY_ANONYMOUS
13
+ end
14
+ end
11
15
 
12
16
  #
13
17
  # The base class for a GSS authentication provider. This class defines a common interface and is not usable as a
@@ -26,6 +30,11 @@ module RubySMB
26
30
  # Whether or not anonymous authentication attempts should be permitted.
27
31
  #
28
32
  attr_accessor :allow_anonymous
33
+
34
+ #
35
+ # Whether or not unknown users should be allowed to authenticate as guests.
36
+ #
37
+ attr_accessor :allow_guests
29
38
  end
30
39
  end
31
40
  end
@@ -80,12 +80,14 @@ module RubySMB
80
80
  if gss_result.nt_status == WindowsError::NTStatus::STATUS_SUCCESS
81
81
  session.state = :valid
82
82
  session.user_id = gss_result.identity
83
+ session.is_guest = !!gss_result.is_guest
83
84
  session.key = @gss_authenticator.session_key
84
- session.signing_required = request.security_mode.signing_required == 1
85
+ session.signing_required = request.security_mode.signing_required == 1 || (!session.is_guest && !session.is_anonymous)
85
86
 
86
87
  response.smb2_header.credits = 32
87
- @cipher_id = 0 if session.is_anonymous # disable encryption for anonymous users
88
+ @cipher_id = 0 if session.is_anonymous || session.is_guest # disable encryption for anonymous users and guest users which have a null session key
88
89
  response.session_flags.encrypt_data = 1 unless @cipher_id == 0
90
+ response.session_flags.guest = session.is_guest
89
91
  elsif gss_result.nt_status == WindowsError::NTStatus::STATUS_MORE_PROCESSING_REQUIRED && @dialect == '0x0311'
90
92
  update_preauth_hash(response)
91
93
  end
@@ -19,7 +19,7 @@ module RubySMB
19
19
  include RubySMB::Server::ServerClient::ShareIO
20
20
  include RubySMB::Server::ServerClient::TreeConnect
21
21
 
22
- attr_reader :dialect, :session_table
22
+ attr_reader :dialect, :dispatcher, :session_table
23
23
 
24
24
  # @param [Server] server the server that accepted this connection
25
25
  # @param [Dispatcher::Socket] dispatcher the connection's socket dispatcher
@@ -57,6 +57,14 @@ module RubySMB
57
57
  @dispatcher.tcp_socket.getpeername
58
58
  end
59
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
+
60
68
  #
61
69
  # Handle a request after the dialect has been negotiated. This is the main
62
70
  # handler for all requests after the connection has been established. If a
@@ -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
@@ -9,27 +9,31 @@ module RubySMB
9
9
  module Close
10
10
  def do_close_smb1(request)
11
11
  # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/99b767e2-8f0e-438b-ace5-4323940f2dc8
12
- if @handles.delete(request.parameter_block.fid).nil?
12
+ handle = @handles.delete(request.parameter_block.fid)
13
+ if handle.nil?
13
14
  response = RubySMB::SMB1::Packet::EmptyPacket.new
14
15
  response.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE
15
16
  return response
16
17
  end
17
18
 
19
+ handle.file.close if handle.file
20
+
18
21
  response = RubySMB::SMB1::Packet::CloseResponse.new
19
22
  response
20
23
  end
21
24
 
22
25
  def do_close_smb2(request)
23
- local_path = get_local_path(request.file_id)
24
- if local_path.nil?
26
+ handle = @handles.delete(request.file_id.to_binary_s)
27
+ if handle.nil?
25
28
  response = RubySMB::SMB2::Packet::ErrorPacket.new
26
29
  response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
27
30
  return response
28
31
  end
29
32
 
30
- @handles.delete(request.file_id.to_binary_s)
33
+ handle.file.close if handle.file
34
+
31
35
  response = RubySMB::SMB2::Packet::CloseResponse.new
32
- set_common_info(response, local_path)
36
+ set_common_info(response, handle.local_path)
33
37
  response.flags = 1
34
38
  response
35
39
  end
@@ -38,7 +38,7 @@ module RubySMB
38
38
  block.allocation_size = get_allocation_size(local_path)
39
39
  end
40
40
 
41
- @handles[response.parameter_block.fid] = Handle.new(path, local_path, false)
41
+ @handles[response.parameter_block.fid] = Handle.new(path, local_path, false, local_path.file? ? local_path.open : nil)
42
42
  response
43
43
  end
44
44
 
@@ -131,7 +131,7 @@ module RubySMB
131
131
  response.contexts_length = 0
132
132
  end
133
133
 
134
- @handles[response.file_id.to_binary_s] = Handle.new(path, local_path, durable)
134
+ @handles[response.file_id.to_binary_s] = Handle.new(path, local_path, durable, local_path.file? ? local_path.open : nil)
135
135
  response
136
136
  end
137
137
  end
@@ -315,7 +315,7 @@ module RubySMB
315
315
 
316
316
  case request.file_information_class
317
317
  when Fscc::FileInformation::FILE_NORMALIZED_NAME_INFORMATION
318
- info = Fscc::FileInformation::FileNameInformation.new(file_name: @handles[request.file_id.to_binary_s].remote_path)
318
+ info = Fscc::FileInformation::FileNormalizedNameInformation.new(file_name: @handles[request.file_id.to_binary_s].remote_path)
319
319
  else
320
320
  info = build_fscc_file_information(local_path, request.file_information_class)
321
321
  end
@@ -344,7 +344,7 @@ module RubySMB
344
344
  volume_label: provider.name
345
345
  )
346
346
  else
347
- logger.warn("Can not handle QUERY_INFO request for type: #{request.info_type}, class: #{request.file_information_class}")
347
+ logger.warn("Can not handle QUERY_INFO request for type: #{request.info_type}, class: #{request.file_information_class} (#{Fscc::FileSystemInformation.name(request.file_information_class)})")
348
348
  raise NotImplementedError
349
349
  end
350
350
 
@@ -9,18 +9,16 @@ module RubySMB
9
9
  module Read
10
10
  def do_read_andx_smb1(request)
11
11
  # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/bb8fcb6a-3032-46a1-ad4a-c0d7892921f9
12
- local_path = get_local_path(request.parameter_block.fid)
12
+ handle = @handles[request.parameter_block.fid]
13
13
 
14
- if local_path.nil?
14
+ if handle.nil? || handle.file.nil?
15
15
  response = SMB1::Packet::EmptyPacket.new
16
16
  response.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE
17
+ return response
17
18
  end
18
19
 
19
- buffer = nil
20
- local_path.open do |file|
21
- file.seek(request.parameter_block.offset.snapshot)
22
- buffer = file.read(request.parameter_block.max_count_of_bytes_to_return.snapshot)
23
- end
20
+ handle.file.seek(request.parameter_block.offset.snapshot)
21
+ buffer = handle.file.read(request.parameter_block.max_count_of_bytes_to_return.snapshot)
24
22
 
25
23
  response = SMB1::Packet::ReadAndxResponse.new
26
24
  response.parameter_block.available = 0xffff # this field is only used for named pipes, must be -1 for all others
@@ -33,20 +31,23 @@ module RubySMB
33
31
  end
34
32
 
35
33
  def do_read_smb2(request)
36
- local_path = get_local_path(request.file_id)
37
- if local_path.nil?
34
+ handle = @handles[request.file_id.to_binary_s]
35
+ if handle.nil?
38
36
  response = RubySMB::SMB2::Packet::ErrorPacket.new
39
37
  response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
40
38
  return response
41
39
  end
42
40
 
41
+ if handle.file.nil?
42
+ response = RubySMB::SMB2::Packet::ErrorPacket.new
43
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE
44
+ return response
45
+ end
46
+
43
47
  raise NotImplementedError unless request.channel == SMB2::SMB2_CHANNEL_NONE
44
48
 
45
- buffer = nil
46
- local_path.open do |file|
47
- file.seek(request.offset.snapshot)
48
- buffer = file.read(request.read_length)
49
- end
49
+ handle.file.seek(request.offset.snapshot)
50
+ buffer = handle.file.read(request.read_length)
50
51
 
51
52
  # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/21e8b343-34e9-4fca-8d93-03dd2d3e961e
52
53
  if buffer.nil? || buffer.length == 0 || buffer.length < request.min_bytes
@@ -1,3 +1,4 @@
1
+ require 'zlib'
1
2
  require 'ruby_smb/server/share/provider/processor'
2
3
 
3
4
  module RubySMB
@@ -16,7 +17,7 @@ module RubySMB
16
17
  include RubySMB::Server::Share::Provider::Disk::Processor::Query
17
18
  include RubySMB::Server::Share::Provider::Disk::Processor::Read
18
19
 
19
- Handle = Struct.new(:remote_path, :local_path, :durable?)
20
+ Handle = Struct.new(:remote_path, :local_path, :durable?, :file)
20
21
  def initialize(provider, server_client, session)
21
22
  super
22
23
  @handles = {}
@@ -30,6 +31,25 @@ module RubySMB
30
31
  )
31
32
  end
32
33
 
34
+ # Build an access mask bit field for the specified path. The return type is a DirectoryAccessMask if path
35
+ # is a directory, otherwise it's a FileAccessMask.
36
+ #
37
+ # @param Pathname path the path to build an access mask for
38
+ # @return [DirectoryAccessMask, FileAccessMask] the access mask
39
+ def smb2_access_mask(path)
40
+ # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/b3af3aaf-9271-4419-b326-eba0341df7d2
41
+ if path.directory?
42
+ am = SMB2::BitField::DirectoryAccessMask.new
43
+ am.traverse = true
44
+ am.list = true
45
+ else
46
+ am = SMB2::BitField::FileAccessMask.new
47
+ am.read_data = true
48
+ end
49
+ am.read_attr = true
50
+ am
51
+ end
52
+
33
53
  private
34
54
 
35
55
  def build_fscc_file_attributes(path)
@@ -44,6 +64,29 @@ module RubySMB
44
64
 
45
65
  def build_fscc_file_information(path, info_class, rename: nil)
46
66
  case info_class
67
+ when Fscc::FileInformation::FILE_ACCESS_INFORMATION
68
+ info = Fscc::FileInformation::FileAccessInformation.new
69
+ # smb2_access_mask returns back either file or directory access mask depending on what path is,
70
+ # FileAccessInformation however isn't defined to account for this context so set it from the binary
71
+ # value
72
+ info.access_flags.read(smb2_access_mask(path).to_binary_s)
73
+ when Fscc::FileInformation::FILE_ALIGNMENT_INFORMATION
74
+ info = Fscc::FileInformation::FileAlignmentInformation.new
75
+ when Fscc::FileInformation::FILE_ALL_INFORMATION
76
+ info = Fscc::FileInformation::FileAllInformation.new
77
+ info.basic_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_BASIC_INFORMATION, rename: rename)
78
+ info.standard_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_STANDARD_INFORMATION, rename: rename)
79
+ info.internal_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_INTERNAL_INFORMATION, rename: rename)
80
+ info.ea_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_EA_INFORMATION, rename: rename)
81
+ info.access_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_ACCESS_INFORMATION, rename: rename)
82
+ info.position_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_POSITION_INFORMATION, rename: rename)
83
+ info.mode_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_MODE_INFORMATION, rename: rename)
84
+ info.alignment_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_ALIGNMENT_INFORMATION, rename: rename)
85
+ info.name_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_NAME_INFORMATION, rename: rename)
86
+ when Fscc::FileInformation::FILE_BASIC_INFORMATION
87
+ info = Fscc::FileInformation::FileBasicInformation.new
88
+ set_common_timestamps(info, path)
89
+ info.file_attributes = build_fscc_file_attributes(path)
47
90
  when Fscc::FileInformation::FILE_EA_INFORMATION
48
91
  info = Fscc::FileInformation::FileEaInformation.new
49
92
  when Fscc::FileInformation::FILE_FULL_DIRECTORY_INFORMATION
@@ -54,21 +97,42 @@ module RubySMB
54
97
  info = Fscc::FileInformation::FileIdBothDirectoryInformation.new
55
98
  set_common_info(info, path)
56
99
  info.file_name = rename || path.basename.to_s
100
+ when Fscc::FileInformation::FILE_ID_FULL_DIRECTORY_INFORMATION
101
+ info = Fscc::FileInformation::FileIdFullDirectoryInformation.new
102
+ set_common_info(info, path)
103
+ info.file_name = rename || path.basename.to_s
104
+ when Fscc::FileInformation::FILE_INTERNAL_INFORMATION
105
+ info = Fscc::FileInformation::FileInternalInformation.new
106
+ info.file_id = Zlib::crc32(path.to_s)
107
+ when Fscc::FileInformation::FILE_MODE_INFORMATION
108
+ info = Fscc::FileInformation::FileModeInformation.new
109
+ when Fscc::FileInformation::FILE_NAME_INFORMATION
110
+ info = Fscc::FileInformation::FileNameInformation.new
111
+ info.file_name = rename || path.basename.to_s
57
112
  when Fscc::FileInformation::FILE_NETWORK_OPEN_INFORMATION
58
113
  info = Fscc::FileInformation::FileNetworkOpenInformation.new
59
114
  set_common_info(info, path)
115
+ when Fscc::FileInformation::FILE_POSITION_INFORMATION
116
+ info = Fscc::FileInformation::FilePositionInformation.new
117
+ info.current_byte_offset = path.size
118
+ when Fscc::FileInformation::FILE_STANDARD_INFORMATION
119
+ info = Fscc::FileInformation::FileStandardInformation.new
120
+ info.allocation_size = get_allocation_size(path)
121
+ info.end_of_file = path.size
122
+ info.directory = path.directory? ? 1 : 0
60
123
  when Fscc::FileInformation::FILE_STREAM_INFORMATION
61
- unless path.file?
62
- raise NotImplementedError, 'Can only generate FILE_STREAM_INFORMATION for files'
63
- end
64
-
65
- info = Fscc::FileInformation::FileStreamInformation.new(
66
- stream_size: path.size,
67
- stream_allocation_size: get_allocation_size(path),
68
- stream_name: '::$DATA'
69
- )
124
+ unless path.file?
125
+ raise NotImplementedError, 'Can only generate FILE_STREAM_INFORMATION for files'
126
+ end
127
+
128
+ info = Fscc::FileInformation::FileStreamInformation.new(
129
+ stream_size: path.size,
130
+ stream_allocation_size: get_allocation_size(path),
131
+ stream_name: '::$DATA'
132
+ )
70
133
  else
71
- raise NotImplementedError, "Unsupported FSCC file information class: #{info_class} (#{Fscc::FileInformation.name(info_class)})"
134
+ logger.warn("Unsupported FSCC file information class: #{info_class} (#{Fscc::FileInformation.name(info_class)})")
135
+ raise NotImplementedError
72
136
  end
73
137
 
74
138
  # some have a next offset field that needs to be set accordingly
@@ -96,7 +160,7 @@ module RubySMB
96
160
  path = path.encode.gsub(/\/|\\/, File::SEPARATOR)
97
161
  path = path.delete_prefix(File::SEPARATOR)
98
162
  local_path = (provider.path + path.encode).cleanpath
99
- unless local_path == provider.path || local_path.to_s.start_with?(provider.path.to_s + '/')
163
+ unless local_path == provider.path || local_path.to_s.start_with?(provider.path.to_s.delete_suffix(File::SEPARATOR) + File::SEPARATOR)
100
164
  raise RuntimeError, "Directory traversal detected to: #{local_path}"
101
165
  end
102
166
  else
@@ -5,14 +5,20 @@ module RubySMB
5
5
  class Server
6
6
  module Share
7
7
  module Provider
8
+ # This is a share provider that exposes the local file system.
8
9
  class Disk < Base
9
10
  TYPE = TYPE_DISK
10
11
  # emulate NTFS just like Samba does
11
12
  FILE_SYSTEM = FileSystem::NTFS
12
13
 
14
+ # @param [String] name The name of this share.
15
+ # @param [String, Pathname] path The local file system path to share. This path must be an absolute path to an existing
16
+ # directory.
13
17
  def initialize(name, path)
14
- path = Pathname.new(File.expand_path(path))
15
- raise ArgumentError unless path.directory?
18
+ path = Pathname.new(File.expand_path(path)) if path.is_a?(String)
19
+ raise ArgumentError.new('path must be a directory') unless path.directory? # it needs to exist
20
+ raise ArgumentError.new('path must be absolute') unless path.absolute? # it needs to be absolute so it is independent of the cwd
21
+
16
22
  @path = path
17
23
  super(name)
18
24
  end
@@ -0,0 +1,85 @@
1
+ require 'ruby_smb/server/share/provider/virtual_disk/virtual_pathname'
2
+ require 'ruby_smb/server/share/provider/virtual_disk/virtual_stat'
3
+
4
+ module RubySMB
5
+ class Server
6
+ module Share
7
+ module Provider
8
+ class VirtualDisk < Disk
9
+ # A dynamic file is one whose contents are generated by the specified
10
+ # block.
11
+ class VirtualDynamicFile < VirtualPathname
12
+ # @param [Hash] disk The mapping of paths to objects representing the virtual file system.
13
+ # @param [String] path The path of this entry.
14
+ # @param [File::Stat] stat An explicit stat object describing the file.
15
+ def initialize(disk, path, stat: nil, &block)
16
+ raise ArgumentError.new('a generation block must be specified') if block.nil?
17
+
18
+ @content_generator = block
19
+ super(disk, path)
20
+ @stat = stat
21
+ end
22
+
23
+ def generate(server_client, session)
24
+ content = @content_generator.call(server_client, session)
25
+ VirtualStaticFile.new(@virtual_disk, @path, content, stat: @stat)
26
+ end
27
+ end
28
+
29
+ # A static file is one whose contents are known at creation time and
30
+ # do not change.
31
+ class VirtualStaticFile < VirtualPathname
32
+ # @param [Hash] disk The mapping of paths to objects representing the virtual file system.
33
+ # @param [String] path The path of this entry.
34
+ # @param [String] content The static content of this file.
35
+ # @param [File::Stat] stat An explicit stat object describing the file.
36
+ def initialize(disk, path, content, stat: nil)
37
+ stat = stat || VirtualStat.new(file?: true, size: content.size)
38
+ raise ArgumentError.new('stat is not a file') unless stat.file?
39
+
40
+ @content = content
41
+ super(disk, path, stat: stat)
42
+ end
43
+
44
+ def open(mode = 'r', &block)
45
+ file = StringIO.new(@content)
46
+ block_given? ? block.call(file) : file
47
+ end
48
+
49
+ attr_reader :content
50
+ end
51
+
52
+ # A mapped file is one who is backed by an entry on disk. The path
53
+ # need not be present, but if it does exist, it must be a file.
54
+ class VirtualMappedFile < VirtualPathname
55
+ # @param [Hash] disk The mapping of paths to objects representing the virtual file system.
56
+ # @param [String] path The path of this entry.
57
+ # @param [String, Pathname] mapped_path The path on the local file system to map into the virtual file system.
58
+ def initialize(disk, path, mapped_path)
59
+ mapped_path = Pathname.new(File.expand_path(mapped_path)) if mapped_path.is_a?(String)
60
+ raise ArgumentError.new('mapped_path must be absolute') unless mapped_path.absolute? # it needs to be absolute so it is independent of the cwd
61
+
62
+ @virtual_disk = disk
63
+ @path = path
64
+ @mapped_path = mapped_path
65
+ end
66
+
67
+ def exist?
68
+ # filter out anything that's not a directory but allow the file to be missing, this prevents exposing
69
+ # directories which could yield path confusion errors
70
+ @mapped_path.exist? && @mapped_path.file?
71
+ end
72
+
73
+ def stat
74
+ @mapped_path.stat
75
+ end
76
+
77
+ def open(mode = 'r', &block)
78
+ @mapped_path.open(mode, &block)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end