ruby_smb 3.1.1 → 3.1.2

Sign up to get free protection for your applications and to get access to all the features.
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