ruby_smb 3.0.6 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/examples/file_server.rb +8 -1
  4. data/examples/virtual_file_server.rb +143 -0
  5. data/lib/ruby_smb/client/encryption.rb +16 -4
  6. data/lib/ruby_smb/client/negotiation.rb +10 -8
  7. data/lib/ruby_smb/fscc/file_information/file_access_information.rb +15 -0
  8. data/lib/ruby_smb/fscc/file_information/file_alignment_information.rb +45 -0
  9. data/lib/ruby_smb/fscc/file_information/file_all_information.rb +23 -0
  10. data/lib/ruby_smb/fscc/file_information/file_basic_information.rb +20 -0
  11. data/lib/ruby_smb/fscc/file_information/file_both_directory_information.rb +3 -3
  12. data/lib/ruby_smb/fscc/file_information/file_directory_information.rb +3 -3
  13. data/lib/ruby_smb/fscc/file_information/file_ea_information.rb +1 -0
  14. data/lib/ruby_smb/fscc/file_information/file_full_directory_information.rb +3 -3
  15. data/lib/ruby_smb/fscc/file_information/file_id_both_directory_information.rb +3 -3
  16. data/lib/ruby_smb/fscc/file_information/file_id_full_directory_information.rb +3 -3
  17. data/lib/ruby_smb/fscc/file_information/file_internal_information.rb +15 -0
  18. data/lib/ruby_smb/fscc/file_information/file_mode_information.rb +29 -0
  19. data/lib/ruby_smb/fscc/file_information/file_name_information.rb +16 -0
  20. data/lib/ruby_smb/fscc/file_information/file_names_information.rb +1 -1
  21. data/lib/ruby_smb/fscc/file_information/file_normalized_name_information.rb +16 -0
  22. data/lib/ruby_smb/fscc/file_information/file_position_information.rb +15 -0
  23. data/lib/ruby_smb/fscc/file_information/file_rename_information.rb +1 -1
  24. data/lib/ruby_smb/fscc/file_information/file_standard_information.rb +20 -0
  25. data/lib/ruby_smb/fscc/file_information/file_stream_information.rb +3 -0
  26. data/lib/ruby_smb/fscc/file_information.rb +43 -6
  27. data/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information.rb +1 -0
  28. data/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information.rb +1 -0
  29. data/lib/ruby_smb/fscc/file_system_information.rb +4 -0
  30. data/lib/ruby_smb/gss/provider/ntlm.rb +20 -3
  31. data/lib/ruby_smb/gss/provider.rb +10 -1
  32. data/lib/ruby_smb/server/server_client/encryption.rb +66 -0
  33. data/lib/ruby_smb/server/server_client/negotiation.rb +14 -3
  34. data/lib/ruby_smb/server/server_client/session_setup.rb +21 -4
  35. data/lib/ruby_smb/server/server_client/share_io.rb +17 -0
  36. data/lib/ruby_smb/server/server_client/tree_connect.rb +40 -3
  37. data/lib/ruby_smb/server/server_client.rb +156 -38
  38. data/lib/ruby_smb/server/session.rb +5 -1
  39. data/lib/ruby_smb/server/share/provider/disk/file_system.rb +28 -0
  40. data/lib/ruby_smb/server/share/provider/disk/processor/close.rb +46 -0
  41. data/lib/ruby_smb/server/share/provider/disk/processor/create.rb +143 -0
  42. data/lib/ruby_smb/server/share/provider/disk/processor/query.rb +359 -0
  43. data/lib/ruby_smb/server/share/provider/disk/processor/read.rb +70 -0
  44. data/lib/ruby_smb/server/share/provider/disk/processor.rb +223 -0
  45. data/lib/ruby_smb/server/share/provider/disk.rb +12 -418
  46. data/lib/ruby_smb/server/share/provider/pipe.rb +2 -2
  47. data/lib/ruby_smb/server/share/provider/processor.rb +16 -0
  48. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_file.rb +85 -0
  49. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname.rb +196 -0
  50. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat.rb +175 -0
  51. data/lib/ruby_smb/server/share/provider/virtual_disk.rb +116 -0
  52. data/lib/ruby_smb/server/share/provider.rb +1 -0
  53. data/lib/ruby_smb/server.rb +13 -3
  54. data/lib/ruby_smb/signing.rb +18 -4
  55. data/lib/ruby_smb/smb1/commands.rb +1 -0
  56. data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +11 -1
  57. data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +1 -1
  58. data/lib/ruby_smb/smb1/packet/read_andx_response.rb +5 -4
  59. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +12 -4
  60. data/lib/ruby_smb/smb1/packet/trans2/data_block.rb +9 -1
  61. data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +52 -51
  62. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +37 -37
  63. data/lib/ruby_smb/smb1/packet/trans2/find_information_level/find_file_both_directory_info.rb +48 -0
  64. data/lib/ruby_smb/smb1/packet/trans2/find_information_level.rb +28 -15
  65. data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +51 -51
  66. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +36 -36
  67. data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +40 -39
  68. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +40 -40
  69. data/lib/ruby_smb/smb1/packet/trans2/query_file_information_request.rb +60 -0
  70. data/lib/ruby_smb/smb1/packet/trans2/query_file_information_response.rb +59 -0
  71. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level/query_fs_attribute_info.rb +31 -0
  72. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level.rb +40 -0
  73. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request.rb +46 -0
  74. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response.rb +59 -0
  75. data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_basic_info.rb +23 -0
  76. data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_standard_info.rb +22 -0
  77. data/lib/ruby_smb/smb1/packet/trans2/query_information_level.rb +62 -0
  78. data/lib/ruby_smb/smb1/packet/trans2/query_path_information_request.rb +65 -0
  79. data/lib/ruby_smb/smb1/packet/trans2/query_path_information_response.rb +59 -0
  80. data/lib/ruby_smb/smb1/packet/trans2/request.rb +24 -8
  81. data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +4 -4
  82. data/lib/ruby_smb/smb1/packet/trans2/response.rb +29 -20
  83. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +42 -42
  84. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +23 -23
  85. data/lib/ruby_smb/smb1/packet/trans2/subcommands.rb +23 -5
  86. data/lib/ruby_smb/smb1/packet/trans2.rb +4 -0
  87. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +4 -1
  88. data/lib/ruby_smb/smb2/negotiate_context.rb +10 -1
  89. data/lib/ruby_smb/smb2/packet/transform_header.rb +7 -7
  90. data/lib/ruby_smb/smb2/tree.rb +1 -0
  91. data/lib/ruby_smb/smb2.rb +1 -0
  92. data/lib/ruby_smb/version.rb +1 -1
  93. data/spec/lib/ruby_smb/client_spec.rb +31 -8
  94. data/spec/lib/ruby_smb/fscc/file_information/file_access_information_spec.rb +21 -0
  95. data/spec/lib/ruby_smb/fscc/file_information/file_alignment_information_spec.rb +21 -0
  96. data/spec/lib/ruby_smb/fscc/file_information/file_all_information_spec.rb +61 -0
  97. data/spec/lib/ruby_smb/fscc/file_information/file_basic_information_spec.rb +41 -0
  98. data/spec/lib/ruby_smb/fscc/file_information/file_both_directory_information_spec.rb +59 -10
  99. data/spec/lib/ruby_smb/fscc/file_information/file_directory_information_spec.rb +30 -12
  100. data/spec/lib/ruby_smb/fscc/file_information/file_ea_information_spec.rb +21 -0
  101. data/spec/lib/ruby_smb/fscc/file_information/file_full_directory_information_spec.rb +30 -12
  102. data/spec/lib/ruby_smb/fscc/file_information/file_id_both_directory_information_spec.rb +63 -10
  103. data/spec/lib/ruby_smb/fscc/file_information/file_id_full_directory_information_spec.rb +30 -12
  104. data/spec/lib/ruby_smb/fscc/file_information/file_internal_information_spec.rb +21 -0
  105. data/spec/lib/ruby_smb/fscc/file_information/file_mode_information_spec.rb +21 -0
  106. data/spec/lib/ruby_smb/fscc/file_information/file_name_information_spec.rb +44 -0
  107. data/spec/lib/ruby_smb/fscc/file_information/file_names_information_spec.rb +30 -12
  108. data/spec/lib/ruby_smb/fscc/file_information/file_network_open_information_spec.rb +51 -0
  109. data/spec/lib/ruby_smb/fscc/file_information/file_normalized_name_information_spec.rb +44 -0
  110. data/spec/lib/ruby_smb/fscc/file_information/file_position_information_spec.rb +21 -0
  111. data/spec/lib/ruby_smb/fscc/file_information/file_rename_information_spec.rb +1 -1
  112. data/spec/lib/ruby_smb/fscc/file_information/file_standard_information_spec.rb +41 -0
  113. data/spec/lib/ruby_smb/fscc/file_information/file_stream_information_spec.rb +51 -0
  114. data/spec/lib/ruby_smb/fscc/file_information_spec.rb +14 -0
  115. data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information_spec.rb +46 -0
  116. data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information_spec.rb +51 -0
  117. data/spec/lib/ruby_smb/fscc/file_system_information_spec.rb +14 -0
  118. data/spec/lib/ruby_smb/server/server_client_spec.rb +15 -0
  119. data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname_spec.rb +581 -0
  120. data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat_spec.rb +207 -0
  121. data/spec/lib/ruby_smb/server/share/provider/virtual_disk_spec.rb +122 -0
  122. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_request_spec.rb +2 -2
  123. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +36 -2
  124. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_request_spec.rb +2 -2
  125. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +35 -1
  126. data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_request_spec.rb +74 -0
  127. data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_response_spec.rb +96 -0
  128. data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request_spec.rb +62 -0
  129. data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response_spec.rb +88 -0
  130. data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_request_spec.rb +79 -0
  131. data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_response_spec.rb +96 -0
  132. data/spec/lib/ruby_smb/smb1/packet/trans2/request_spec.rb +2 -2
  133. data/spec/lib/ruby_smb/smb1/packet/trans2/response_spec.rb +3 -3
  134. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_request_spec.rb +3 -2
  135. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb +7 -2
  136. data/spec/lib/ruby_smb/smb1/tree_spec.rb +3 -3
  137. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +2 -2
  138. data.tar.gz.sig +0 -0
  139. metadata +88 -2
  140. metadata.gz.sig +0 -0
@@ -1,430 +1,24 @@
1
- require 'zlib'
2
- require 'ruby_smb/server/share/provider/processor'
1
+ require 'ruby_smb/server/share/provider/disk/file_system'
2
+ require 'ruby_smb/server/share/provider/disk/processor'
3
3
 
4
4
  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
- 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
11
+ # emulate NTFS just like Samba does
12
+ FILE_SYSTEM = FileSystem::NTFS
424
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.
425
17
  def initialize(name, path)
426
- path = Pathname.new(File.expand_path(path))
427
- 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
+
428
22
  @path = path
429
23
  super(name)
430
24
  end
@@ -6,12 +6,12 @@ module RubySMB
6
6
  module Provider
7
7
  class Pipe < Base
8
8
  TYPE = TYPE_PIPE
9
- class Processor < Processor::Base
9
+ class Processor < Provider::Processor::Base
10
10
  end
11
11
  end
12
12
 
13
13
  class IpcPipe < Pipe
14
- class Processor < Processor::Base
14
+ class Processor < Provider::Processor::Base
15
15
  def maximal_access(path=nil)
16
16
  RubySMB::SMB2::BitField::DirectoryAccessMask.read([0x001f00a9].pack('V'))
17
17
  end
@@ -25,6 +25,22 @@ module RubySMB
25
25
  def disconnect!
26
26
  end
27
27
 
28
+ def do_close_smb1(request)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ def do_nt_create_andx_smb1(request)
33
+ raise NotImplementedError
34
+ end
35
+
36
+ def do_read_andx_smb1(request)
37
+ raise NotImplementedError
38
+ end
39
+
40
+ def do_transactions2_smb1(request)
41
+ raise NotImplementedError
42
+ end
43
+
28
44
  def do_close_smb2(request)
29
45
  raise NotImplementedError
30
46
  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