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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/examples/file_server.rb +8 -1
- data/examples/virtual_file_server.rb +143 -0
- data/lib/ruby_smb/client/encryption.rb +16 -4
- data/lib/ruby_smb/client/negotiation.rb +10 -8
- data/lib/ruby_smb/fscc/file_information/file_access_information.rb +15 -0
- data/lib/ruby_smb/fscc/file_information/file_alignment_information.rb +45 -0
- data/lib/ruby_smb/fscc/file_information/file_all_information.rb +23 -0
- data/lib/ruby_smb/fscc/file_information/file_basic_information.rb +20 -0
- data/lib/ruby_smb/fscc/file_information/file_both_directory_information.rb +3 -3
- data/lib/ruby_smb/fscc/file_information/file_directory_information.rb +3 -3
- data/lib/ruby_smb/fscc/file_information/file_ea_information.rb +1 -0
- data/lib/ruby_smb/fscc/file_information/file_full_directory_information.rb +3 -3
- data/lib/ruby_smb/fscc/file_information/file_id_both_directory_information.rb +3 -3
- data/lib/ruby_smb/fscc/file_information/file_id_full_directory_information.rb +3 -3
- data/lib/ruby_smb/fscc/file_information/file_internal_information.rb +15 -0
- data/lib/ruby_smb/fscc/file_information/file_mode_information.rb +29 -0
- data/lib/ruby_smb/fscc/file_information/file_name_information.rb +16 -0
- data/lib/ruby_smb/fscc/file_information/file_names_information.rb +1 -1
- data/lib/ruby_smb/fscc/file_information/file_normalized_name_information.rb +16 -0
- data/lib/ruby_smb/fscc/file_information/file_position_information.rb +15 -0
- data/lib/ruby_smb/fscc/file_information/file_rename_information.rb +1 -1
- data/lib/ruby_smb/fscc/file_information/file_standard_information.rb +20 -0
- data/lib/ruby_smb/fscc/file_information/file_stream_information.rb +3 -0
- data/lib/ruby_smb/fscc/file_information.rb +43 -6
- data/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information.rb +1 -0
- data/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information.rb +1 -0
- data/lib/ruby_smb/fscc/file_system_information.rb +4 -0
- data/lib/ruby_smb/gss/provider/ntlm.rb +20 -3
- data/lib/ruby_smb/gss/provider.rb +10 -1
- data/lib/ruby_smb/server/server_client/encryption.rb +66 -0
- data/lib/ruby_smb/server/server_client/negotiation.rb +14 -3
- data/lib/ruby_smb/server/server_client/session_setup.rb +21 -4
- data/lib/ruby_smb/server/server_client/share_io.rb +17 -0
- data/lib/ruby_smb/server/server_client/tree_connect.rb +40 -3
- data/lib/ruby_smb/server/server_client.rb +156 -38
- data/lib/ruby_smb/server/session.rb +5 -1
- data/lib/ruby_smb/server/share/provider/disk/file_system.rb +28 -0
- data/lib/ruby_smb/server/share/provider/disk/processor/close.rb +46 -0
- data/lib/ruby_smb/server/share/provider/disk/processor/create.rb +143 -0
- data/lib/ruby_smb/server/share/provider/disk/processor/query.rb +359 -0
- data/lib/ruby_smb/server/share/provider/disk/processor/read.rb +70 -0
- data/lib/ruby_smb/server/share/provider/disk/processor.rb +223 -0
- data/lib/ruby_smb/server/share/provider/disk.rb +12 -418
- data/lib/ruby_smb/server/share/provider/pipe.rb +2 -2
- data/lib/ruby_smb/server/share/provider/processor.rb +16 -0
- data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_file.rb +85 -0
- data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname.rb +196 -0
- data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat.rb +175 -0
- data/lib/ruby_smb/server/share/provider/virtual_disk.rb +116 -0
- data/lib/ruby_smb/server/share/provider.rb +1 -0
- data/lib/ruby_smb/server.rb +13 -3
- data/lib/ruby_smb/signing.rb +18 -4
- data/lib/ruby_smb/smb1/commands.rb +1 -0
- data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +11 -1
- data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/read_andx_response.rb +5 -4
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +12 -4
- data/lib/ruby_smb/smb1/packet/trans2/data_block.rb +9 -1
- data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +52 -51
- data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +37 -37
- data/lib/ruby_smb/smb1/packet/trans2/find_information_level/find_file_both_directory_info.rb +48 -0
- data/lib/ruby_smb/smb1/packet/trans2/find_information_level.rb +28 -15
- data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +51 -51
- data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +36 -36
- data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +40 -39
- data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +40 -40
- data/lib/ruby_smb/smb1/packet/trans2/query_file_information_request.rb +60 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_file_information_response.rb +59 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level/query_fs_attribute_info.rb +31 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level.rb +40 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request.rb +46 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response.rb +59 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_basic_info.rb +23 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_standard_info.rb +22 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_information_level.rb +62 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_path_information_request.rb +65 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_path_information_response.rb +59 -0
- data/lib/ruby_smb/smb1/packet/trans2/request.rb +24 -8
- data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +4 -4
- data/lib/ruby_smb/smb1/packet/trans2/response.rb +29 -20
- data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +42 -42
- data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +23 -23
- data/lib/ruby_smb/smb1/packet/trans2/subcommands.rb +23 -5
- data/lib/ruby_smb/smb1/packet/trans2.rb +4 -0
- data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +4 -1
- data/lib/ruby_smb/smb2/negotiate_context.rb +10 -1
- data/lib/ruby_smb/smb2/packet/transform_header.rb +7 -7
- data/lib/ruby_smb/smb2/tree.rb +1 -0
- data/lib/ruby_smb/smb2.rb +1 -0
- data/lib/ruby_smb/version.rb +1 -1
- data/spec/lib/ruby_smb/client_spec.rb +31 -8
- data/spec/lib/ruby_smb/fscc/file_information/file_access_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_alignment_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_all_information_spec.rb +61 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_basic_information_spec.rb +41 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_both_directory_information_spec.rb +59 -10
- data/spec/lib/ruby_smb/fscc/file_information/file_directory_information_spec.rb +30 -12
- data/spec/lib/ruby_smb/fscc/file_information/file_ea_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_full_directory_information_spec.rb +30 -12
- data/spec/lib/ruby_smb/fscc/file_information/file_id_both_directory_information_spec.rb +63 -10
- data/spec/lib/ruby_smb/fscc/file_information/file_id_full_directory_information_spec.rb +30 -12
- data/spec/lib/ruby_smb/fscc/file_information/file_internal_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_mode_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_name_information_spec.rb +44 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_names_information_spec.rb +30 -12
- data/spec/lib/ruby_smb/fscc/file_information/file_network_open_information_spec.rb +51 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_normalized_name_information_spec.rb +44 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_position_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_rename_information_spec.rb +1 -1
- data/spec/lib/ruby_smb/fscc/file_information/file_standard_information_spec.rb +41 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_stream_information_spec.rb +51 -0
- data/spec/lib/ruby_smb/fscc/file_information_spec.rb +14 -0
- data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information_spec.rb +46 -0
- data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information_spec.rb +51 -0
- data/spec/lib/ruby_smb/fscc/file_system_information_spec.rb +14 -0
- data/spec/lib/ruby_smb/server/server_client_spec.rb +15 -0
- data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname_spec.rb +581 -0
- data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat_spec.rb +207 -0
- data/spec/lib/ruby_smb/server/share/provider/virtual_disk_spec.rb +122 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +36 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +35 -1
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_request_spec.rb +74 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_response_spec.rb +96 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request_spec.rb +62 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response_spec.rb +88 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_request_spec.rb +79 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_response_spec.rb +96 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/response_spec.rb +3 -3
- data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_request_spec.rb +3 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb +7 -2
- data/spec/lib/ruby_smb/smb1/tree_spec.rb +3 -3
- data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +88 -2
- metadata.gz.sig +0 -0
@@ -1,430 +1,24 @@
|
|
1
|
-
require '
|
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
|
-
|
11
|
-
|
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
|