ruby_smb 3.0.6 → 3.1.0

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 (78) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/ruby_smb/client/encryption.rb +16 -4
  4. data/lib/ruby_smb/client/negotiation.rb +3 -1
  5. data/lib/ruby_smb/fscc/file_information.rb +4 -0
  6. data/lib/ruby_smb/server/server_client/encryption.rb +66 -0
  7. data/lib/ruby_smb/server/server_client/negotiation.rb +14 -3
  8. data/lib/ruby_smb/server/server_client/session_setup.rb +18 -3
  9. data/lib/ruby_smb/server/server_client/share_io.rb +17 -0
  10. data/lib/ruby_smb/server/server_client/tree_connect.rb +40 -3
  11. data/lib/ruby_smb/server/server_client.rb +147 -37
  12. data/lib/ruby_smb/server/share/provider/disk/file_system.rb +28 -0
  13. data/lib/ruby_smb/server/share/provider/disk/processor/close.rb +42 -0
  14. data/lib/ruby_smb/server/share/provider/disk/processor/create.rb +143 -0
  15. data/lib/ruby_smb/server/share/provider/disk/processor/query.rb +359 -0
  16. data/lib/ruby_smb/server/share/provider/disk/processor/read.rb +69 -0
  17. data/lib/ruby_smb/server/share/provider/disk/processor.rb +159 -0
  18. data/lib/ruby_smb/server/share/provider/disk.rb +4 -416
  19. data/lib/ruby_smb/server/share/provider/pipe.rb +2 -2
  20. data/lib/ruby_smb/server/share/provider/processor.rb +16 -0
  21. data/lib/ruby_smb/signing.rb +18 -4
  22. data/lib/ruby_smb/smb1/commands.rb +1 -0
  23. data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +11 -1
  24. data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +1 -1
  25. data/lib/ruby_smb/smb1/packet/read_andx_response.rb +5 -4
  26. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +12 -4
  27. data/lib/ruby_smb/smb1/packet/trans2/data_block.rb +9 -1
  28. data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +52 -51
  29. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +37 -37
  30. data/lib/ruby_smb/smb1/packet/trans2/find_information_level/find_file_both_directory_info.rb +48 -0
  31. data/lib/ruby_smb/smb1/packet/trans2/find_information_level.rb +28 -15
  32. data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +51 -51
  33. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +36 -36
  34. data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +40 -39
  35. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +40 -40
  36. data/lib/ruby_smb/smb1/packet/trans2/query_file_information_request.rb +60 -0
  37. data/lib/ruby_smb/smb1/packet/trans2/query_file_information_response.rb +59 -0
  38. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level/query_fs_attribute_info.rb +31 -0
  39. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level.rb +40 -0
  40. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request.rb +46 -0
  41. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response.rb +59 -0
  42. data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_basic_info.rb +23 -0
  43. data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_standard_info.rb +22 -0
  44. data/lib/ruby_smb/smb1/packet/trans2/query_information_level.rb +62 -0
  45. data/lib/ruby_smb/smb1/packet/trans2/query_path_information_request.rb +65 -0
  46. data/lib/ruby_smb/smb1/packet/trans2/query_path_information_response.rb +59 -0
  47. data/lib/ruby_smb/smb1/packet/trans2/request.rb +24 -8
  48. data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +4 -4
  49. data/lib/ruby_smb/smb1/packet/trans2/response.rb +29 -20
  50. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +42 -42
  51. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +23 -23
  52. data/lib/ruby_smb/smb1/packet/trans2/subcommands.rb +23 -5
  53. data/lib/ruby_smb/smb1/packet/trans2.rb +4 -0
  54. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +4 -1
  55. data/lib/ruby_smb/smb2/negotiate_context.rb +10 -1
  56. data/lib/ruby_smb/smb2/packet/transform_header.rb +7 -7
  57. data/lib/ruby_smb/smb2.rb +1 -0
  58. data/lib/ruby_smb/version.rb +1 -1
  59. data/spec/lib/ruby_smb/client_spec.rb +20 -6
  60. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_request_spec.rb +2 -2
  61. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +36 -2
  62. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_request_spec.rb +2 -2
  63. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +35 -1
  64. data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_request_spec.rb +74 -0
  65. data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_response_spec.rb +96 -0
  66. data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request_spec.rb +62 -0
  67. data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response_spec.rb +88 -0
  68. data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_request_spec.rb +79 -0
  69. data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_response_spec.rb +96 -0
  70. data/spec/lib/ruby_smb/smb1/packet/trans2/request_spec.rb +2 -2
  71. data/spec/lib/ruby_smb/smb1/packet/trans2/response_spec.rb +3 -3
  72. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_request_spec.rb +3 -2
  73. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb +7 -2
  74. data/spec/lib/ruby_smb/smb1/tree_spec.rb +3 -3
  75. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +2 -2
  76. data.tar.gz.sig +0 -0
  77. metadata +33 -2
  78. metadata.gz.sig +0 -0
@@ -0,0 +1,159 @@
1
+ require 'ruby_smb/server/share/provider/processor'
2
+
3
+ module RubySMB
4
+ class Server
5
+ module Share
6
+ module Provider
7
+ class Disk < Base
8
+ class Processor < Provider::Processor::Base
9
+ require 'ruby_smb/server/share/provider/disk/processor/close'
10
+ require 'ruby_smb/server/share/provider/disk/processor/create'
11
+ require 'ruby_smb/server/share/provider/disk/processor/query'
12
+ require 'ruby_smb/server/share/provider/disk/processor/read'
13
+
14
+ include RubySMB::Server::Share::Provider::Disk::Processor::Close
15
+ include RubySMB::Server::Share::Provider::Disk::Processor::Create
16
+ include RubySMB::Server::Share::Provider::Disk::Processor::Query
17
+ include RubySMB::Server::Share::Provider::Disk::Processor::Read
18
+
19
+ Handle = Struct.new(:remote_path, :local_path, :durable?)
20
+ def initialize(provider, server_client, session)
21
+ super
22
+ @handles = {}
23
+ @query_directory_context = {}
24
+ end
25
+
26
+ def maximal_access(path=nil)
27
+ RubySMB::SMB2::BitField::FileAccessMask.new(
28
+ read_attr: 1,
29
+ read_data: 1
30
+ )
31
+ end
32
+
33
+ private
34
+
35
+ def build_fscc_file_attributes(path)
36
+ file_attributes = Fscc::FileAttributes.new
37
+ if path.file?
38
+ file_attributes.normal = 1
39
+ elsif path.directory?
40
+ file_attributes.directory = 1
41
+ end
42
+ file_attributes
43
+ end
44
+
45
+ def build_fscc_file_information(path, info_class, rename: nil)
46
+ case info_class
47
+ when Fscc::FileInformation::FILE_EA_INFORMATION
48
+ info = Fscc::FileInformation::FileEaInformation.new
49
+ when Fscc::FileInformation::FILE_FULL_DIRECTORY_INFORMATION
50
+ info = Fscc::FileInformation::FileFullDirectoryInformation.new
51
+ set_common_info(info, path)
52
+ info.file_name = rename || path.basename.to_s
53
+ when Fscc::FileInformation::FILE_ID_BOTH_DIRECTORY_INFORMATION
54
+ info = Fscc::FileInformation::FileIdBothDirectoryInformation.new
55
+ set_common_info(info, path)
56
+ info.file_name = rename || path.basename.to_s
57
+ when Fscc::FileInformation::FILE_NETWORK_OPEN_INFORMATION
58
+ info = Fscc::FileInformation::FileNetworkOpenInformation.new
59
+ set_common_info(info, path)
60
+ 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
+ )
70
+ else
71
+ raise NotImplementedError, "Unsupported FSCC file information class: #{info_class} (#{Fscc::FileInformation.name(info_class)})"
72
+ end
73
+
74
+ # some have a next offset field that needs to be set accordingly
75
+ if info.respond_to?(:next_offset)
76
+ align = 8
77
+ info.next_offset = info.num_bytes + ((align - info.num_bytes % align) % align)
78
+ end
79
+
80
+ info
81
+ end
82
+
83
+ def get_allocation_size(path)
84
+ (path.size + (4095 - (path.size + 4095) % 4096))
85
+ end
86
+
87
+ def get_local_path(path)
88
+ case path
89
+ # SMB1 uses uint16_t file IDs
90
+ when ::BinData::Uint16le
91
+ local_path = @handles[path]&.local_path
92
+ # SMB2 uses a compound field for file IDs, so convert it to the binary rep and use that as the key
93
+ when Field::Smb2Fileid
94
+ local_path = @handles[path.to_binary_s]&.local_path
95
+ when ::String
96
+ path = path.encode.gsub(/\/|\\/, File::SEPARATOR)
97
+ path = path.delete_prefix(File::SEPARATOR)
98
+ local_path = (provider.path + path.encode).cleanpath
99
+ unless local_path == provider.path || local_path.to_s.start_with?(provider.path.to_s + '/')
100
+ raise RuntimeError, "Directory traversal detected to: #{local_path}"
101
+ end
102
+ else
103
+ raise NotImplementedError, "Can not get the local path for: #{path.inspect}, type: #{path.class.inspect}"
104
+ end
105
+
106
+ local_path
107
+ end
108
+
109
+ # A bunch of structures have these common fields with the same meaning, so set them all here
110
+ def set_common_info(info, path)
111
+ set_common_timestamps(info, path)
112
+ if path.file?
113
+ info.end_of_file = path.size
114
+ info.allocation_size = get_allocation_size(path)
115
+ end
116
+ info.file_attributes = build_fscc_file_attributes(path)
117
+ end
118
+
119
+ def set_common_timestamps(info, path)
120
+ begin
121
+ info.create_time = path.birthtime
122
+ rescue NotImplementedError
123
+ logger.warn("The file system does not support #birthtime for #{path}")
124
+ end
125
+
126
+ info.last_access = path.atime
127
+ info.last_write = path.mtime
128
+ info.last_change = path.ctime
129
+ end
130
+
131
+ # Turn a wildcard expression into a regex. Not all wildcard
132
+ # characters are supported. Wildcards that can not be converted will
133
+ # raise a NotImplementedError.
134
+ #
135
+ # @param [String] wildcard The wildcard expression to convert.
136
+ # @return [Regexp] The converted expression.
137
+ # @raise [NotImplementedError] Raised when the wildcard can not be
138
+ # converted.
139
+ def wildcard_to_regex(wildcard)
140
+ return Regexp.new('.*') if ['*.*', ''].include?(wildcard)
141
+
142
+ if wildcard.each_char.any? { |c| c == '<' || c == '>' }
143
+ # the < > wildcard operators are not supported
144
+ raise NotImplementedError, 'Unsupported wild card characters'
145
+ end
146
+
147
+ wildcard = Regexp.escape(wildcard)
148
+ wildcard = wildcard.gsub(/(\\\?)+$/) { |match| ".{0,#{match.length / 2}}"}
149
+ wildcard = wildcard.gsub('\?', '.')
150
+ wildcard = wildcard.gsub('\*', '.*')
151
+ wildcard = wildcard.gsub('"', '\.')
152
+ Regexp.new('^' + wildcard + '$')
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -1,5 +1,5 @@
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
@@ -7,420 +7,8 @@ module RubySMB
7
7
  module Provider
8
8
  class Disk < Base
9
9
  TYPE = TYPE_DISK
10
- class Processor < Processor::Base
11
- Handle = Struct.new(:remote_path, :local_path, :durable?)
12
- def initialize(provider, server_client, session)
13
- super
14
- @handles = {}
15
- @query_directory_context = {}
16
- end
17
-
18
- def maximal_access(path=nil)
19
- RubySMB::SMB2::BitField::FileAccessMask.new(
20
- read_attr: 1,
21
- read_data: 1
22
- )
23
- end
24
-
25
- def do_close_smb2(request)
26
- local_path = get_local_path(request.file_id)
27
- if local_path.nil?
28
- response = RubySMB::SMB2::Packet::ErrorPacket.new
29
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
30
- return response
31
- end
32
-
33
- @handles.delete(request.file_id.to_binary_s)
34
- response = RubySMB::SMB2::Packet::CloseResponse.new
35
- set_common_info(response, local_path)
36
- response.flags = 1
37
- response
38
- end
39
-
40
- def do_create_smb2(request)
41
- unless request.create_disposition == RubySMB::Dispositions::FILE_OPEN
42
- logger.warn("Can not handle CREATE request for disposition: #{request.create_disposition}")
43
- raise NotImplementedError
44
- end
45
-
46
- # process the delayed io fields
47
- request.name.read_now!
48
- unless request.contexts_offset == 0
49
- request.contexts.read_now!
50
- request.contexts.each do |context|
51
- context.name.read_now!
52
- context.data.read_now!
53
- end
54
- end
55
-
56
- path = request.name.snapshot
57
- path = path.encode.gsub('\\', File::SEPARATOR)
58
- local_path = get_local_path(path)
59
- unless local_path && (local_path.file? || local_path.directory?)
60
- logger.warn("Requested path does not exist: #{local_path}")
61
- response = RubySMB::SMB2::Packet::ErrorPacket.new
62
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND
63
- return response
64
- end
65
-
66
- durable = false
67
- response = RubySMB::SMB2::Packet::CreateResponse.new
68
- response.create_action = RubySMB::CreateActions::FILE_OPENED
69
- set_common_info(response, local_path)
70
- response.file_id.persistent = Zlib::crc32(path)
71
- response.file_id.volatile = rand(0xffffffff)
72
-
73
- request.contexts.each do |req_ctx|
74
- case req_ctx.name
75
- when SMB2::CreateContext::CREATE_DURABLE_HANDLE
76
- # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/9adbc354-5fad-40e7-9a62-4a4b6c1ff8a0
77
- next if request.contexts.any? { |ctx| ctx.name == SMB2::CreateContext::CREATE_DURABLE_HANDLE_RECONNECT }
78
-
79
- if request.contexts.any? { |ctx| [ SMB2::CreateContext::CREATE_DURABLE_HANDLE_V2, SMB2::CreateContext::CREATE_DURABLE_HANDLE_RECONNECT_v2 ].include?(ctx.name) }
80
- response = RubySMB::SMB2::Packet::ErrorPacket.new
81
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER
82
- return response
83
- end
84
-
85
- durable = true
86
- res_ctx = SMB2::CreateContext::CreateDurableHandleResponse.new
87
- when SMB2::CreateContext::CREATE_DURABLE_HANDLE_V2
88
- # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/33e6800a-adf5-4221-af27-7e089b9e81d1
89
- if request.contexts.any? { |ctx| [ SMB2::CreateContext::CREATE_DURABLE_HANDLE, SMB2::CreateContext::CREATE_DURABLE_HANDLE_RECONNECT, SMB2::CreateContext::CREATE_DURABLE_HANDLE_RECONNECT_v2 ].include?(ctx.name) }
90
- response = RubySMB::SMB2::Packet::ErrorPacket.new
91
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER
92
- return response
93
- end
94
-
95
- durable = true
96
- res_ctx = SMB2::CreateContext::CreateDurableHandleV2Response.new(
97
- timeout: 1000,
98
- flags: req_ctx.data.flags
99
- )
100
- when SMB2::CreateContext::CREATE_QUERY_MAXIMAL_ACCESS
101
- res_ctx = SMB2::CreateContext::CreateQueryMaximalAccessResponse.new(
102
- maximal_access: maximal_access(path)
103
- )
104
- when SMB2::CreateContext::CREATE_QUERY_ON_DISK_ID
105
- res_ctx = SMB2::CreateContext::CreateQueryOnDiskIdResponse.new(
106
- disk_file_id: local_path.stat.ino,
107
- volume_id: local_path.stat.dev
108
- )
109
- else
110
- logger.warn("Can not handle CREATE context: #{req_ctx.name}")
111
- next
112
- end
113
-
114
- response.contexts << SMB2::CreateContext::CreateContextResponse.new(name: res_ctx.class::NAME, data: res_ctx)
115
- end
116
-
117
- if response.contexts.length > 0
118
- # fixup the offsets
119
- response.contexts[0...-1].each do |ctx|
120
- ctx.next_offset = ctx.num_bytes
121
- end
122
- response.contexts[-1].next_offset = 0
123
- response.contexts_offset = response.buffer.abs_offset
124
- response.contexts_length = response.buffer.num_bytes
125
- else
126
- response.contexts_offset = 0
127
- response.contexts_length = 0
128
- end
129
-
130
- @handles[response.file_id.to_binary_s] = Handle.new(path, local_path, durable)
131
- response
132
- end
133
-
134
- def do_query_directory_smb2(request)
135
- local_path = get_local_path(request.file_id)
136
- if local_path.nil?
137
- response = RubySMB::SMB2::Packet::ErrorPacket.new
138
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
139
- return response
140
- end
141
-
142
- # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/29dfcc9b-3aec-406b-abb5-0b4fe96712e2
143
- info_class = request.file_information_class.snapshot
144
- begin
145
- # probe #build_info to see if it supports the requested info class
146
- build_info(Pathname.new(__FILE__), info_class)
147
- rescue NotImplementedError
148
- logger.warn("Can not handle QUERY_DIRECTORY request for class: #{info_class}")
149
- raise
150
- end
151
-
152
- unless local_path.directory?
153
- response = SMB2::Packet::ErrorPacket.new
154
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER
155
- return response
156
- end
157
-
158
- search_pattern = request.name.snapshot.dup.encode
159
- begin
160
- search_regex = wildcard_to_regex(search_pattern)
161
- rescue NotImplementedError
162
- logger.warn("Can not handle QUERY_DIRECTORY wildcard pattern: #{search_pattern}")
163
- raise
164
- end
165
-
166
- return_single = request.flags.return_single == 1
167
-
168
- align = 8
169
- infos = []
170
- total_size = 0
171
-
172
- if @query_directory_context[request.file_id.to_binary_s].nil? || request.flags.reopen == 1 || request.flags.restart_scans == 1
173
- dirents = local_path.children.sort.to_a
174
- dirents.unshift(local_path.parent) unless local_path.parent == local_path
175
- dirents.unshift(local_path)
176
- @query_directory_context[request.file_id.to_binary_s] = dirents
177
- else
178
- dirents = @query_directory_context[request.file_id.to_binary_s]
179
- end
180
-
181
- while dirents.length > 0
182
- dirent = dirents.shift
183
- next unless dirent.file? || dirent.directory? # filter out everything but files and directories
184
-
185
- case dirent
186
- when local_path
187
- dirent_name = '.'
188
- when local_path.parent
189
- dirent_name = '..'
190
- else
191
- dirent_name = dirent.basename.to_s
192
- end
193
- next unless search_regex.match?(dirent_name)
194
-
195
- info = build_info(dirent, info_class, rename: dirent_name)
196
- info_size = info.num_bytes + ((align - info.num_bytes % align) % align)
197
- if total_size + info_size > request.output_length
198
- dirents.unshift(dirent) # no space left for this one so put it back
199
- break
200
- end
201
-
202
- infos << info
203
- total_size += info_size
204
- break if return_single
205
- end
206
-
207
- if infos.length == 0
208
- response = SMB2::Packet::QueryDirectoryResponse.new
209
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NO_MORE_FILES
210
- return response
211
- end
212
-
213
- response = SMB2::Packet::QueryDirectoryResponse.new
214
- infos.last.next_offset = 0 if infos.last
215
- buffer = ""
216
- infos.each do |info|
217
- info = info.to_binary_s
218
- buffer << info + "\x00".b * ((align - info.length % align) % align)
219
- end
220
- response.buffer = buffer
221
- response
222
- end
223
-
224
- def do_query_info_smb2(request)
225
- local_path = get_local_path(request.file_id)
226
- if local_path.nil?
227
- response = RubySMB::SMB2::Packet::ErrorPacket.new
228
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
229
- return response
230
- end
231
-
232
- case request.info_type
233
- when SMB2::SMB2_INFO_FILE
234
- info = query_info_smb2_file(request, local_path)
235
- when SMB2::SMB2_INFO_FILESYSTEM
236
- info = query_info_smb2_filesystem(request, local_path)
237
- else
238
- logger.warn("Can not handle QUERY_INFO request for type: #{request.info_type}, class: #{request.file_information_class}")
239
- raise NotImplementedError
240
- end
241
-
242
- response = SMB2::Packet::QueryInfoResponse.new
243
- response.buffer = info.to_binary_s
244
- response
245
- end
246
-
247
- def do_read_smb2(request)
248
- local_path = get_local_path(request.file_id)
249
- if local_path.nil?
250
- response = RubySMB::SMB2::Packet::ErrorPacket.new
251
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
252
- return response
253
- end
254
-
255
- raise NotImplementedError unless request.channel == SMB2::SMB2_CHANNEL_NONE
256
-
257
- buffer = nil
258
- local_path.open do |file|
259
- file.seek(request.offset.snapshot)
260
- buffer = file.read(request.read_length)
261
- end
262
-
263
- # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/21e8b343-34e9-4fca-8d93-03dd2d3e961e
264
- if buffer.nil? || buffer.length == 0 || buffer.length < request.min_bytes
265
- response = SMB2::Packet::ErrorPacket.new
266
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_END_OF_FILE
267
- return response
268
- end
269
-
270
- response = SMB2::Packet::ReadResponse.new
271
- response.data_length = buffer.length
272
- response.buffer = buffer
273
- response
274
- end
275
-
276
- private
277
-
278
- def build_file_attributes(path)
279
- file_attributes = Fscc::FileAttributes.new
280
- if path.file?
281
- file_attributes.normal = 1
282
- elsif path.directory?
283
- file_attributes.directory = 1
284
- end
285
- file_attributes
286
- end
287
-
288
- def build_info(path, info_class, rename: nil)
289
- case info_class
290
- when Fscc::FileInformation::FILE_ID_BOTH_DIRECTORY_INFORMATION
291
- info = Fscc::FileInformation::FileIdBothDirectoryInformation.new
292
- set_common_info(info, path)
293
- info.file_name = rename || path.basename.to_s
294
- when Fscc::FileInformation::FILE_FULL_DIRECTORY_INFORMATION
295
- info = Fscc::FileInformation::FileFullDirectoryInformation.new
296
- set_common_info(info, path)
297
- info.file_name = rename || path.basename.to_s
298
- else
299
- raise NotImplementedError, "unsupported info class: #{info_class}"
300
- end
301
-
302
- align = 8
303
- info.next_offset = info.num_bytes + ((align - info.num_bytes % align) % align)
304
- info
305
- end
306
-
307
- def get_allocation_size(path)
308
- (path.size + (4095 - (path.size + 4095) % 4096))
309
- end
310
-
311
- def get_local_path(path)
312
- case path
313
- when Field::Smb2Fileid
314
- local_path = @handles[path.to_binary_s]&.local_path
315
- when ::String
316
- local_path = (provider.path + path.encode).cleanpath
317
- # TODO: report / handle directory traversal issues more robustly
318
- raise RuntimeError unless local_path == provider.path || local_path.to_s.start_with?(provider.path.to_s + '/')
319
- else
320
- raise NotImplementedError, "Can not get the local path for: #{path.inspect}"
321
- end
322
-
323
- local_path
324
- end
325
-
326
- def query_info_smb2_file(request, local_path)
327
- raise ArgumentError unless request.info_type == SMB2::SMB2_INFO_FILE
328
-
329
- case request.file_information_class
330
- when Fscc::FileInformation::FILE_EA_INFORMATION
331
- info = Fscc::FileInformation::FileEaInformation.new
332
- when Fscc::FileInformation::FILE_NETWORK_OPEN_INFORMATION
333
- info = Fscc::FileInformation::FileNetworkOpenInformation.new
334
- set_common_info(info, local_path)
335
- when Fscc::FileInformation::FILE_NORMALIZED_NAME_INFORMATION
336
- info = Fscc::FileInformation::FileNameInformation.new(file_name: @handles[request.file_id.to_binary_s].remote_path)
337
- when Fscc::FileInformation::FILE_STREAM_INFORMATION
338
- raise NotImplementedError unless local_path.file?
339
-
340
- info = Fscc::FileInformation::FileStreamInformation.new(
341
- stream_size: local_path.size,
342
- stream_allocation_size: get_allocation_size(local_path),
343
- stream_name: '::$DATA'
344
- )
345
- else
346
- logger.warn("Can not handle QUERY_INFO request for type: #{request.info_type}, class: #{request.file_information_class}")
347
- raise NotImplementedError
348
- end
349
-
350
- info
351
- end
352
-
353
- def query_info_smb2_filesystem(request, local_path)
354
- raise ArgumentError unless request.info_type == SMB2::SMB2_INFO_FILESYSTEM
355
-
356
- case request.file_information_class
357
- when Fscc::FileSystemInformation::FILE_FS_ATTRIBUTE_INFORMATION
358
- # emulate NTFS just like Samba does
359
- info = Fscc::FileSystemInformation::FileFsAttributeInformation.new(
360
- file_system_attributes: {
361
- file_case_sensitive_search: 1,
362
- file_case_preserved_names: 1,
363
- file_unicode_on_disk: 1,
364
- file_supports_object_ids: 1,
365
- },
366
- maximum_component_name_length: 255,
367
- file_system_name: 'NTFS'
368
- )
369
- when Fscc::FileSystemInformation::FILE_FS_VOLUME_INFORMATION
370
- info = Fscc::FileSystemInformation::FileFsVolumeInformation.new(
371
- volume_serial_number: provider.path.stat.ino,
372
- volume_label: provider.name
373
- )
374
- else
375
- logger.warn("Can not handle QUERY_INFO request for type: #{request.info_type}, class: #{request.file_information_class}")
376
- raise NotImplementedError
377
- end
378
-
379
- info
380
- end
381
-
382
- # A bunch of structures have these common fields with the same meaning, so set them all here
383
- def set_common_info(info, path)
384
- begin
385
- info.create_time = path.birthtime
386
- rescue NotImplementedError
387
- logger.warn("The file system does not support #birthtime for #{path}")
388
- end
389
-
390
- info.last_access = path.atime
391
- info.last_write = path.mtime
392
- info.last_change = path.ctime
393
- if path.file?
394
- info.end_of_file = path.size
395
- info.allocation_size = get_allocation_size(path)
396
- end
397
- info.file_attributes = build_file_attributes(path)
398
- end
399
-
400
- # Turn a wildcard expression into a regex. Not all wildcard
401
- # characters are supported. Wildcards that can not be converted will
402
- # raise a NotImplementedError.
403
- #
404
- # @param [String] wildcard The wildcard expression to convert.
405
- # @return [Regexp] The converted expression.
406
- # @raise [NotImplementedError] Raised when the wildcard can not be
407
- # converted.
408
- def wildcard_to_regex(wildcard)
409
- return Regexp.new('.*') if ['*.*', ''].include?(wildcard)
410
-
411
- if wildcard.each_char.any? { |c| c == '<' || c == '>' }
412
- # the < > wildcard operators are not supported
413
- raise NotImplementedError
414
- end
415
-
416
- wildcard = Regexp.escape(wildcard)
417
- wildcard = wildcard.gsub(/(\\\?)+$/) { |match| ".{0,#{match.length / 2}}"}
418
- wildcard = wildcard.gsub('\?', '.')
419
- wildcard = wildcard.gsub('\*', '.*')
420
- wildcard = wildcard.gsub('"', '\.')
421
- Regexp.new('^' + wildcard + '$')
422
- end
423
- end
10
+ # emulate NTFS just like Samba does
11
+ FILE_SYSTEM = FileSystem::NTFS
424
12
 
425
13
  def initialize(name, path)
426
14
  path = Pathname.new(File.expand_path(path))
@@ -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
@@ -6,17 +6,31 @@ module RubySMB
6
6
  # @return [String]
7
7
  attr_accessor :session_key
8
8
 
9
- # Take an SMB1 packet and sign it.
9
+ # Take an SMB1 packet and sign it. This version is an instance method that
10
+ # accesses the necessary values from the object instance.
10
11
  #
11
12
  # @param [RubySMB::GenericPacket] packet The packet to sign.
12
13
  # @return [RubySMB::GenericPacket] the signed packet
13
14
  def smb1_sign(packet)
15
+ packet = Signing::smb1_sign(packet, @session_key, @sequence_counter)
16
+ @sequence_counter += 1
17
+
18
+ packet
19
+ end
20
+
21
+ # Take an SMB1 packet and sign it. This version is a module function that
22
+ # requires the necessary values to be explicitly passed to it.
23
+ #
24
+ # @param [RubySMB::GenericPacket] packet The packet to sign.
25
+ # @param [String] session_key The key to use for signing.
26
+ # @param [Integer] sequence_counter The sequence counter of packet to be sent.
27
+ # @return [RubySMB::GenericPacket] the signed packet
28
+ def self.smb1_sign(packet, session_key, sequence_counter)
14
29
  # Pack the Sequence counter into a int64le
15
- packed_sequence_counter = [@sequence_counter].pack('Q<')
30
+ packed_sequence_counter = [sequence_counter].pack('Q<')
16
31
  packet.smb_header.security_features = packed_sequence_counter
17
- signature = OpenSSL::Digest::MD5.digest(@session_key + packet.to_binary_s)[0, 8]
32
+ signature = OpenSSL::Digest::MD5.digest(session_key + packet.to_binary_s)[0, 8]
18
33
  packet.smb_header.security_features = signature
19
- @sequence_counter += 1
20
34
 
21
35
  packet
22
36
  end
@@ -12,6 +12,7 @@ module RubySMB
12
12
  SMB_COM_NEGOTIATE = 0x72
13
13
  SMB_COM_SESSION_SETUP_ANDX = 0x73
14
14
  SMB_COM_LOGOFF = 0x74
15
+ SMB_COM_LOGOFF_ANDX = 0x74
15
16
  SMB_COM_TREE_CONNECT = 0x75
16
17
  SMB_COM_NT_TRANSACT = 0xA0
17
18
  SMB_COM_NT_TRANSACT_SECONDARY = 0xA1