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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/ruby_smb/client/encryption.rb +16 -4
- data/lib/ruby_smb/client/negotiation.rb +3 -1
- data/lib/ruby_smb/fscc/file_information.rb +4 -0
- 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 +18 -3
- 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 +147 -37
- data/lib/ruby_smb/server/share/provider/disk/file_system.rb +28 -0
- data/lib/ruby_smb/server/share/provider/disk/processor/close.rb +42 -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 +69 -0
- data/lib/ruby_smb/server/share/provider/disk/processor.rb +159 -0
- data/lib/ruby_smb/server/share/provider/disk.rb +4 -416
- 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/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.rb +1 -0
- data/lib/ruby_smb/version.rb +1 -1
- data/spec/lib/ruby_smb/client_spec.rb +20 -6
- 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 +33 -2
- metadata.gz.sig +0 -0
@@ -0,0 +1,42 @@
|
|
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
|
+
module Close
|
10
|
+
def do_close_smb1(request)
|
11
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/99b767e2-8f0e-438b-ace5-4323940f2dc8
|
12
|
+
if @handles.delete(request.parameter_block.fid).nil?
|
13
|
+
response = RubySMB::SMB1::Packet::EmptyPacket.new
|
14
|
+
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE
|
15
|
+
return response
|
16
|
+
end
|
17
|
+
|
18
|
+
response = RubySMB::SMB1::Packet::CloseResponse.new
|
19
|
+
response
|
20
|
+
end
|
21
|
+
|
22
|
+
def do_close_smb2(request)
|
23
|
+
local_path = get_local_path(request.file_id)
|
24
|
+
if local_path.nil?
|
25
|
+
response = RubySMB::SMB2::Packet::ErrorPacket.new
|
26
|
+
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
|
27
|
+
return response
|
28
|
+
end
|
29
|
+
|
30
|
+
@handles.delete(request.file_id.to_binary_s)
|
31
|
+
response = RubySMB::SMB2::Packet::CloseResponse.new
|
32
|
+
set_common_info(response, local_path)
|
33
|
+
response.flags = 1
|
34
|
+
response
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'ruby_smb/server/share/provider/processor'
|
3
|
+
|
4
|
+
module RubySMB
|
5
|
+
class Server
|
6
|
+
module Share
|
7
|
+
module Provider
|
8
|
+
class Disk < Base
|
9
|
+
class Processor < Provider::Processor::Base
|
10
|
+
module Create
|
11
|
+
def do_nt_create_andx_smb1(request)
|
12
|
+
path = request.data_block.file_name.snapshot
|
13
|
+
path = path.encode.gsub(/\/|\\/, File::SEPARATOR)
|
14
|
+
path = path.delete_prefix(File::SEPARATOR)
|
15
|
+
local_path = get_local_path(path)
|
16
|
+
unless local_path && (local_path.file? || local_path.directory?)
|
17
|
+
logger.warn("Requested path does not exist: #{local_path}")
|
18
|
+
response = SMB1::Packet::EmptyPacket.new
|
19
|
+
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_NO_SUCH_FILE
|
20
|
+
return response
|
21
|
+
end
|
22
|
+
|
23
|
+
response = SMB1::Packet::NtCreateAndxResponse.new
|
24
|
+
block = response.parameter_block
|
25
|
+
block.fid = rand(0xffff)
|
26
|
+
# fields are slightly different so #set_common_info can't be used :(
|
27
|
+
begin
|
28
|
+
block.create_time = local_path.birthtime
|
29
|
+
rescue NotImplementedError
|
30
|
+
logger.warn("The file system does not support #birthtime for #{path}")
|
31
|
+
end
|
32
|
+
|
33
|
+
block.last_access_time = local_path.atime
|
34
|
+
block.last_write_time = local_path.mtime
|
35
|
+
block.last_change_time = local_path.ctime
|
36
|
+
if local_path.file?
|
37
|
+
block.end_of_file = local_path.size
|
38
|
+
block.allocation_size = get_allocation_size(local_path)
|
39
|
+
end
|
40
|
+
|
41
|
+
@handles[response.parameter_block.fid] = Handle.new(path, local_path, false)
|
42
|
+
response
|
43
|
+
end
|
44
|
+
|
45
|
+
def do_create_smb2(request)
|
46
|
+
unless request.create_disposition == RubySMB::Dispositions::FILE_OPEN
|
47
|
+
logger.warn("Can not handle CREATE request for disposition: #{request.create_disposition}")
|
48
|
+
raise NotImplementedError
|
49
|
+
end
|
50
|
+
|
51
|
+
# process the delayed io fields
|
52
|
+
request.name.read_now!
|
53
|
+
unless request.contexts_offset == 0
|
54
|
+
request.contexts.read_now!
|
55
|
+
request.contexts.each do |context|
|
56
|
+
context.name.read_now!
|
57
|
+
context.data.read_now!
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
path = request.name.snapshot
|
62
|
+
local_path = get_local_path(path)
|
63
|
+
unless local_path && (local_path.file? || local_path.directory?)
|
64
|
+
logger.warn("Requested path does not exist: #{local_path}")
|
65
|
+
response = RubySMB::SMB2::Packet::ErrorPacket.new
|
66
|
+
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND
|
67
|
+
return response
|
68
|
+
end
|
69
|
+
|
70
|
+
durable = false
|
71
|
+
response = RubySMB::SMB2::Packet::CreateResponse.new
|
72
|
+
response.create_action = RubySMB::CreateActions::FILE_OPENED
|
73
|
+
set_common_info(response, local_path)
|
74
|
+
response.file_id.persistent = Zlib::crc32(path)
|
75
|
+
response.file_id.volatile = rand(0xffffffff)
|
76
|
+
|
77
|
+
request.contexts.each do |req_ctx|
|
78
|
+
case req_ctx.name
|
79
|
+
when SMB2::CreateContext::CREATE_DURABLE_HANDLE
|
80
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/9adbc354-5fad-40e7-9a62-4a4b6c1ff8a0
|
81
|
+
next if request.contexts.any? { |ctx| ctx.name == SMB2::CreateContext::CREATE_DURABLE_HANDLE_RECONNECT }
|
82
|
+
|
83
|
+
if request.contexts.any? { |ctx| [ SMB2::CreateContext::CREATE_DURABLE_HANDLE_V2, SMB2::CreateContext::CREATE_DURABLE_HANDLE_RECONNECT_v2 ].include?(ctx.name) }
|
84
|
+
response = RubySMB::SMB2::Packet::ErrorPacket.new
|
85
|
+
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER
|
86
|
+
return response
|
87
|
+
end
|
88
|
+
|
89
|
+
durable = true
|
90
|
+
res_ctx = SMB2::CreateContext::CreateDurableHandleResponse.new
|
91
|
+
when SMB2::CreateContext::CREATE_DURABLE_HANDLE_V2
|
92
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/33e6800a-adf5-4221-af27-7e089b9e81d1
|
93
|
+
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) }
|
94
|
+
response = RubySMB::SMB2::Packet::ErrorPacket.new
|
95
|
+
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER
|
96
|
+
return response
|
97
|
+
end
|
98
|
+
|
99
|
+
durable = true
|
100
|
+
res_ctx = SMB2::CreateContext::CreateDurableHandleV2Response.new(
|
101
|
+
timeout: 1000,
|
102
|
+
flags: req_ctx.data.flags
|
103
|
+
)
|
104
|
+
when SMB2::CreateContext::CREATE_QUERY_MAXIMAL_ACCESS
|
105
|
+
res_ctx = SMB2::CreateContext::CreateQueryMaximalAccessResponse.new(
|
106
|
+
maximal_access: maximal_access(path)
|
107
|
+
)
|
108
|
+
when SMB2::CreateContext::CREATE_QUERY_ON_DISK_ID
|
109
|
+
res_ctx = SMB2::CreateContext::CreateQueryOnDiskIdResponse.new(
|
110
|
+
disk_file_id: local_path.stat.ino,
|
111
|
+
volume_id: local_path.stat.dev
|
112
|
+
)
|
113
|
+
else
|
114
|
+
logger.warn("Can not handle CREATE context: #{req_ctx.name}")
|
115
|
+
next
|
116
|
+
end
|
117
|
+
|
118
|
+
response.contexts << SMB2::CreateContext::CreateContextResponse.new(name: res_ctx.class::NAME, data: res_ctx)
|
119
|
+
end
|
120
|
+
|
121
|
+
if response.contexts.length > 0
|
122
|
+
# fixup the offsets
|
123
|
+
response.contexts[0...-1].each do |ctx|
|
124
|
+
ctx.next_offset = ctx.num_bytes
|
125
|
+
end
|
126
|
+
response.contexts[-1].next_offset = 0
|
127
|
+
response.contexts_offset = response.buffer.abs_offset
|
128
|
+
response.contexts_length = response.buffer.num_bytes
|
129
|
+
else
|
130
|
+
response.contexts_offset = 0
|
131
|
+
response.contexts_length = 0
|
132
|
+
end
|
133
|
+
|
134
|
+
@handles[response.file_id.to_binary_s] = Handle.new(path, local_path, durable)
|
135
|
+
response
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,359 @@
|
|
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
|
+
module Query
|
10
|
+
def do_transactions2_smb1(request)
|
11
|
+
# can't find an example where more than one setup is set, this code makes alot of assumptions that there
|
12
|
+
# are exactly 0 or 1 entries
|
13
|
+
if request.parameter_block.setup.length > 1
|
14
|
+
raise NotImplementedError, 'There are more than 1 TRANSACTION2 setup values'
|
15
|
+
end
|
16
|
+
|
17
|
+
case request.data_block.trans2_parameters
|
18
|
+
when SMB1::Packet::Trans2::FindFirst2RequestTrans2Parameters
|
19
|
+
response = transaction2_smb1_find_first2(request)
|
20
|
+
when SMB1::Packet::Trans2::QueryFileInformationRequestTrans2Parameters
|
21
|
+
response = transaction2_smb1_query_file_information(request)
|
22
|
+
when SMB1::Packet::Trans2::QueryPathInformationRequestTrans2Parameters
|
23
|
+
response = transaction2_smb1_query_path_information(request)
|
24
|
+
when SMB1::Packet::Trans2::QueryFsInformationRequestTrans2Parameters
|
25
|
+
response = transaction2_smb1_query_fs_information(request)
|
26
|
+
else
|
27
|
+
subcommand = request.parameter_block.setup.first
|
28
|
+
if subcommand
|
29
|
+
logger.warn("Can not handle TRANSACTION2 request for subcommand #{subcommand} (#{SMB1::Packet::Trans2::Subcommands.name(subcommand)})")
|
30
|
+
else
|
31
|
+
logger.warn('Can not handle TRANSACTION2 request with missing subcommand')
|
32
|
+
end
|
33
|
+
raise NotImplementedError
|
34
|
+
end
|
35
|
+
|
36
|
+
if response and response.parameter_block.is_a?(RubySMB::SMB1::Packet::Trans2::Response::ParameterBlock)
|
37
|
+
response.parameter_block.setup = []
|
38
|
+
response.parameter_block.total_parameter_count = response.parameter_block.parameter_count = response.data_block.trans2_parameters.num_bytes
|
39
|
+
response.parameter_block.total_data_count = response.parameter_block.data_count = response.data_block.trans2_data.num_bytes
|
40
|
+
end
|
41
|
+
|
42
|
+
response
|
43
|
+
end
|
44
|
+
|
45
|
+
def do_query_directory_smb2(request)
|
46
|
+
local_path = get_local_path(request.file_id)
|
47
|
+
if local_path.nil?
|
48
|
+
response = RubySMB::SMB2::Packet::ErrorPacket.new
|
49
|
+
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
|
50
|
+
return response
|
51
|
+
end
|
52
|
+
|
53
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/29dfcc9b-3aec-406b-abb5-0b4fe96712e2
|
54
|
+
info_class = request.file_information_class.snapshot
|
55
|
+
begin
|
56
|
+
# probe #build_fscc_file_information to see if it supports the requested info class
|
57
|
+
build_fscc_file_information(Pathname.new(__FILE__), info_class)
|
58
|
+
rescue NotImplementedError
|
59
|
+
logger.warn("Can not handle QUERY_DIRECTORY request for class: #{info_class}")
|
60
|
+
raise
|
61
|
+
end
|
62
|
+
|
63
|
+
unless local_path.directory?
|
64
|
+
response = SMB2::Packet::ErrorPacket.new
|
65
|
+
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER
|
66
|
+
return response
|
67
|
+
end
|
68
|
+
|
69
|
+
search_pattern = request.name.snapshot.dup.encode
|
70
|
+
begin
|
71
|
+
search_regex = wildcard_to_regex(search_pattern)
|
72
|
+
rescue NotImplementedError
|
73
|
+
logger.warn("Can not handle QUERY_DIRECTORY wildcard pattern: #{search_pattern}")
|
74
|
+
raise
|
75
|
+
end
|
76
|
+
|
77
|
+
return_single = request.flags.return_single == 1
|
78
|
+
|
79
|
+
align = 8
|
80
|
+
infos = []
|
81
|
+
total_size = 0
|
82
|
+
|
83
|
+
if @query_directory_context[request.file_id.to_binary_s].nil? || request.flags.reopen == 1 || request.flags.restart_scans == 1
|
84
|
+
dirents = local_path.children.sort.to_a
|
85
|
+
dirents.unshift(local_path.parent) unless local_path.parent == local_path
|
86
|
+
dirents.unshift(local_path)
|
87
|
+
@query_directory_context[request.file_id.to_binary_s] = dirents
|
88
|
+
else
|
89
|
+
dirents = @query_directory_context[request.file_id.to_binary_s]
|
90
|
+
end
|
91
|
+
|
92
|
+
consume_dirents(local_path, dirents, filter_regex: search_regex) do |dirent, dirent_name|
|
93
|
+
info = build_fscc_file_information(dirent, info_class, rename: dirent_name)
|
94
|
+
info_size = info.num_bytes + ((align - info.num_bytes % align) % align)
|
95
|
+
if total_size + info_size > request.output_length
|
96
|
+
dirents.unshift(dirent) # no space left for this one so put it back
|
97
|
+
break
|
98
|
+
end
|
99
|
+
|
100
|
+
infos << info
|
101
|
+
total_size += info_size
|
102
|
+
break if return_single
|
103
|
+
end
|
104
|
+
|
105
|
+
if infos.length == 0
|
106
|
+
response = SMB2::Packet::QueryDirectoryResponse.new
|
107
|
+
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NO_MORE_FILES
|
108
|
+
return response
|
109
|
+
end
|
110
|
+
|
111
|
+
response = SMB2::Packet::QueryDirectoryResponse.new
|
112
|
+
infos.last.next_offset = 0 if infos.last
|
113
|
+
buffer = ""
|
114
|
+
infos.each do |info|
|
115
|
+
info = info.to_binary_s
|
116
|
+
buffer << info + "\x00".b * ((align - info.length % align) % align)
|
117
|
+
end
|
118
|
+
response.buffer = buffer
|
119
|
+
response
|
120
|
+
end
|
121
|
+
|
122
|
+
def do_query_info_smb2(request)
|
123
|
+
local_path = get_local_path(request.file_id)
|
124
|
+
if local_path.nil?
|
125
|
+
response = RubySMB::SMB2::Packet::ErrorPacket.new
|
126
|
+
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
|
127
|
+
return response
|
128
|
+
end
|
129
|
+
|
130
|
+
case request.info_type
|
131
|
+
when SMB2::SMB2_INFO_FILE
|
132
|
+
info = query_info_smb2_file(request, local_path)
|
133
|
+
when SMB2::SMB2_INFO_FILESYSTEM
|
134
|
+
info = query_info_smb2_filesystem(request, local_path)
|
135
|
+
else
|
136
|
+
logger.warn("Can not handle QUERY_INFO request for type: #{request.info_type}, class: #{request.file_information_class}")
|
137
|
+
raise NotImplementedError
|
138
|
+
end
|
139
|
+
|
140
|
+
response = SMB2::Packet::QueryInfoResponse.new
|
141
|
+
response.buffer = info.to_binary_s
|
142
|
+
response
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
# This function iterates over dirents, filtering out entries as necessary. It relies on the fact that
|
148
|
+
# dirents is a mutable object.
|
149
|
+
#
|
150
|
+
# @param Pathmame local_path a path to use for relative location checks
|
151
|
+
# @param [Array<Pathname>] dirents the array of dirents to iterate over
|
152
|
+
# @param Regex filter_regex a regex that when specified will be used to filter out entries that don't match
|
153
|
+
def consume_dirents(local_path, dirents, filter_regex: nil)
|
154
|
+
until dirents.empty?
|
155
|
+
dirent = dirents.shift
|
156
|
+
next unless dirent.file? || dirent.directory? # filter out everything but files and directories
|
157
|
+
|
158
|
+
case dirent
|
159
|
+
when local_path
|
160
|
+
dirent_name = '.'
|
161
|
+
when local_path.parent
|
162
|
+
dirent_name = '..'
|
163
|
+
else
|
164
|
+
dirent_name = dirent.basename.to_s
|
165
|
+
end
|
166
|
+
next unless filter_regex.nil? || filter_regex.match?(dirent_name)
|
167
|
+
|
168
|
+
yield dirent, dirent_name
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def transaction2_smb1_find_first2(request)
|
173
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/f93455dc-2bd7-4698-b91e-8c9c7abd63cf
|
174
|
+
raise ArgumentError unless request.data_block.trans2_parameters.is_a? SMB1::Packet::Trans2::FindFirst2RequestTrans2Parameters
|
175
|
+
|
176
|
+
subdir, _, search_pattern = request.data_block.trans2_parameters.filename.encode.gsub('\\', File::SEPARATOR).rpartition(File::SEPARATOR)
|
177
|
+
local_path = get_local_path(subdir)
|
178
|
+
unless local_path.directory?
|
179
|
+
response = SMB1::Packet::EmptyPacket.new
|
180
|
+
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_NO_SUCH_FILE
|
181
|
+
return response
|
182
|
+
end
|
183
|
+
|
184
|
+
begin
|
185
|
+
search_regex = wildcard_to_regex(search_pattern)
|
186
|
+
rescue NotImplementedError
|
187
|
+
logger.warn("Can not handle TRANSACTION2 FIND_FIRST2 wildcard pattern: #{search_pattern}")
|
188
|
+
raise
|
189
|
+
end
|
190
|
+
|
191
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/29dfcc9b-3aec-406b-abb5-0b4fe96712e2
|
192
|
+
info_level = request.data_block.trans2_parameters.information_level.to_i
|
193
|
+
if info_level == SMB1::Packet::Trans2::FindInformationLevel::SMB_FIND_FILE_FULL_DIRECTORY_INFO
|
194
|
+
info_class = SMB1::Packet::Trans2::FindInformationLevel::FindFileFullDirectoryInfo
|
195
|
+
elsif info_level == SMB1::Packet::Trans2::FindInformationLevel::SMB_FIND_FILE_BOTH_DIRECTORY_INFO
|
196
|
+
info_class = SMB1::Packet::Trans2::FindInformationLevel::FindFileBothDirectoryInfo
|
197
|
+
else
|
198
|
+
logger.warn("Can not handle TRANSACTION2 FIND_FIRST2 request for class: #{info_level} (#{SMB1::Packet::Trans2::FindInformationLevel.name(info_level)})")
|
199
|
+
raise NotImplementedError
|
200
|
+
end
|
201
|
+
|
202
|
+
logger.debug("Handling TRANSACTION2 FIND_FIRST2 request for class: #{info_level} (#{SMB1::Packet::Trans2::FindInformationLevel.name(info_level)})")
|
203
|
+
infos = []
|
204
|
+
dirents = local_path.children.sort.to_a
|
205
|
+
|
206
|
+
consume_dirents(local_path, dirents, filter_regex: search_regex) do |dirent, dirent_name|
|
207
|
+
info = info_class.new
|
208
|
+
info.unicode = request.smb_header.flags2.unicode == 1
|
209
|
+
set_common_timestamps(info, dirent)
|
210
|
+
info.end_of_file = dirent.size
|
211
|
+
info.allocation_size = get_allocation_size(dirent)
|
212
|
+
info.ext_file_attributes.directory = dirent.directory? ? 1 : 0
|
213
|
+
info.ext_file_attributes.read_only = !dirent.writable? ? 1 : 0
|
214
|
+
info.ext_file_attributes.normal = (dirent.file? && dirent.writable?) ? 1 : 0
|
215
|
+
info.file_name = dirent_name
|
216
|
+
info.next_offset = info.num_bytes
|
217
|
+
infos << info
|
218
|
+
end
|
219
|
+
infos.last.next_offset = 0 unless infos.empty?
|
220
|
+
|
221
|
+
response = SMB1::Packet::Trans2::FindFirst2Response.new
|
222
|
+
response.smb_header.flags2.unicode = request.smb_header.flags2.unicode == 1
|
223
|
+
if infos.empty?
|
224
|
+
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_NO_SUCH_FILE
|
225
|
+
else
|
226
|
+
buffer = infos.map(&:to_binary_s).join
|
227
|
+
response.data_block.trans2_parameters.sid = rand(0xffff)
|
228
|
+
response.data_block.trans2_parameters.search_count = infos.length
|
229
|
+
response.data_block.trans2_parameters.eos = 1
|
230
|
+
response.data_block.trans2_data.buffer = buffer
|
231
|
+
end
|
232
|
+
response
|
233
|
+
end
|
234
|
+
|
235
|
+
def transaction2_smb1_query_information(request, response, local_path)
|
236
|
+
unless local_path&.exist?
|
237
|
+
response = SMB1::Packet::EmptyPacket.new
|
238
|
+
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_NO_SUCH_FILE
|
239
|
+
return response
|
240
|
+
end
|
241
|
+
|
242
|
+
case request.data_block.trans2_parameters.information_level
|
243
|
+
when SMB1::Packet::Trans2::QueryInformationLevel::SMB_QUERY_FILE_BASIC_INFO
|
244
|
+
info = SMB1::Packet::Trans2::QueryInformationLevel::QueryFileBasicInfo.new
|
245
|
+
set_common_timestamps(info, local_path)
|
246
|
+
info.ext_file_attributes.directory = local_path.directory? ? 1 : 0
|
247
|
+
info.ext_file_attributes.read_only = !local_path.writable? ? 1 : 0
|
248
|
+
info.ext_file_attributes.normal = (local_path.file? && local_path.writable?) ? 1 : 0
|
249
|
+
when SMB1::Packet::Trans2::QueryInformationLevel::SMB_QUERY_FILE_STANDARD_INFO
|
250
|
+
info = SMB1::Packet::Trans2::QueryInformationLevel::QueryFileStandardInfo.new
|
251
|
+
info.end_of_file = local_path.size
|
252
|
+
info.allocation_size = get_allocation_size(local_path)
|
253
|
+
info.directory = local_path.directory? ? 1 : 0
|
254
|
+
else
|
255
|
+
level = request.data_block.trans2_parameters.information_level
|
256
|
+
logger.warn("Can not handle TRANSACTION2 QUERY request for information level #{level} (#{SMB1::Packet::Trans2::QueryInformationLevel.name(level)})")
|
257
|
+
raise NotImplementedError
|
258
|
+
end
|
259
|
+
|
260
|
+
response.data_block.trans2_data.buffer = info.to_binary_s
|
261
|
+
response
|
262
|
+
end
|
263
|
+
|
264
|
+
def transaction2_smb1_query_file_information(request)
|
265
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/0a96fae0-b183-42b6-92bd-e05b1d92f434
|
266
|
+
raise ArgumentError unless request.data_block.trans2_parameters.is_a? SMB1::Packet::Trans2::QueryFileInformationRequestTrans2Parameters
|
267
|
+
|
268
|
+
local_path = get_local_path(request.data_block.trans2_parameters.fid)
|
269
|
+
response = SMB1::Packet::Trans2::QueryFileInformationResponse.new
|
270
|
+
transaction2_smb1_query_information(request, response, local_path)
|
271
|
+
end
|
272
|
+
|
273
|
+
def transaction2_smb1_query_fs_information(request)
|
274
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/c396398f-2d7f-4356-bac4-326076bafcd1
|
275
|
+
raise ArgumentError unless request.data_block.trans2_parameters.is_a? SMB1::Packet::Trans2::QueryFsInformationRequestTrans2Parameters
|
276
|
+
|
277
|
+
local_path = provider.path
|
278
|
+
unless local_path&.exist?
|
279
|
+
response = SMB1::Packet::EmptyPacket.new
|
280
|
+
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_NO_SUCH_FILE
|
281
|
+
return response
|
282
|
+
end
|
283
|
+
|
284
|
+
response = SMB1::Packet::Trans2::QueryFsInformationResponse.new
|
285
|
+
|
286
|
+
case request.data_block.trans2_parameters.information_level
|
287
|
+
when SMB1::Packet::Trans2::QueryFsInformationLevel::SMB_QUERY_FS_ATTRIBUTE_INFO
|
288
|
+
info = SMB1::Packet::Trans2::QueryFsInformationLevel::QueryFsAttributeInfo.new
|
289
|
+
info.file_system_attributes.file_case_sensitive_search = FILE_SYSTEM.case_sensitive_search
|
290
|
+
info.file_system_attributes.file_case_preserved_names = FILE_SYSTEM.case_preserved_names
|
291
|
+
info.file_system_attributes.file_unicode_on_disk = FILE_SYSTEM.unicode_on_disk
|
292
|
+
info.max_file_name_length_in_bytes = FILE_SYSTEM.max_name_bytes
|
293
|
+
info.file_system_name = FILE_SYSTEM.name
|
294
|
+
else
|
295
|
+
level = request.data_block.trans2_parameters.information_level
|
296
|
+
logger.warn("Can not handle TRANSACTION2 QUERY_FS request for information level #{level} (#{SMB1::Packet::Trans2::QueryFsInformationLevel.name(level)})")
|
297
|
+
raise NotImplementedError
|
298
|
+
end
|
299
|
+
|
300
|
+
response.data_block.trans2_data.buffer = info.to_binary_s
|
301
|
+
response
|
302
|
+
end
|
303
|
+
|
304
|
+
def transaction2_smb1_query_path_information(request)
|
305
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/a5941db4-b992-442a-ba81-fe3378c5bd60
|
306
|
+
raise ArgumentError unless request.data_block.trans2_parameters.is_a? SMB1::Packet::Trans2::QueryPathInformationRequestTrans2Parameters
|
307
|
+
|
308
|
+
local_path = get_local_path(request.data_block.trans2_parameters.filename.encode)
|
309
|
+
response = SMB1::Packet::Trans2::QueryPathInformationResponse.new
|
310
|
+
transaction2_smb1_query_information(request, response, local_path)
|
311
|
+
end
|
312
|
+
|
313
|
+
def query_info_smb2_file(request, local_path)
|
314
|
+
raise ArgumentError unless request.info_type == SMB2::SMB2_INFO_FILE
|
315
|
+
|
316
|
+
case request.file_information_class
|
317
|
+
when Fscc::FileInformation::FILE_NORMALIZED_NAME_INFORMATION
|
318
|
+
info = Fscc::FileInformation::FileNameInformation.new(file_name: @handles[request.file_id.to_binary_s].remote_path)
|
319
|
+
else
|
320
|
+
info = build_fscc_file_information(local_path, request.file_information_class)
|
321
|
+
end
|
322
|
+
|
323
|
+
info
|
324
|
+
end
|
325
|
+
|
326
|
+
def query_info_smb2_filesystem(request, local_path)
|
327
|
+
raise ArgumentError unless request.info_type == SMB2::SMB2_INFO_FILESYSTEM
|
328
|
+
|
329
|
+
case request.file_information_class
|
330
|
+
when Fscc::FileSystemInformation::FILE_FS_ATTRIBUTE_INFORMATION
|
331
|
+
info = Fscc::FileSystemInformation::FileFsAttributeInformation.new(
|
332
|
+
file_system_attributes: {
|
333
|
+
file_case_sensitive_search: FILE_SYSTEM.case_sensitive_search,
|
334
|
+
file_case_preserved_names: FILE_SYSTEM.case_preserved_names,
|
335
|
+
file_unicode_on_disk: FILE_SYSTEM.unicode_on_disk,
|
336
|
+
file_supports_object_ids: 1,
|
337
|
+
},
|
338
|
+
maximum_component_name_length: FILE_SYSTEM.max_name_bytes,
|
339
|
+
file_system_name: FILE_SYSTEM.name
|
340
|
+
)
|
341
|
+
when Fscc::FileSystemInformation::FILE_FS_VOLUME_INFORMATION
|
342
|
+
info = Fscc::FileSystemInformation::FileFsVolumeInformation.new(
|
343
|
+
volume_serial_number: provider.path.stat.ino,
|
344
|
+
volume_label: provider.name
|
345
|
+
)
|
346
|
+
else
|
347
|
+
logger.warn("Can not handle QUERY_INFO request for type: #{request.info_type}, class: #{request.file_information_class}")
|
348
|
+
raise NotImplementedError
|
349
|
+
end
|
350
|
+
|
351
|
+
info
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
@@ -0,0 +1,69 @@
|
|
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
|
+
module Read
|
10
|
+
def do_read_andx_smb1(request)
|
11
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/bb8fcb6a-3032-46a1-ad4a-c0d7892921f9
|
12
|
+
local_path = get_local_path(request.parameter_block.fid)
|
13
|
+
|
14
|
+
if local_path.nil?
|
15
|
+
response = SMB1::Packet::EmptyPacket.new
|
16
|
+
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE
|
17
|
+
end
|
18
|
+
|
19
|
+
buffer = nil
|
20
|
+
local_path.open do |file|
|
21
|
+
file.seek(request.parameter_block.offset.snapshot)
|
22
|
+
buffer = file.read(request.parameter_block.max_count_of_bytes_to_return.snapshot)
|
23
|
+
end
|
24
|
+
|
25
|
+
response = SMB1::Packet::ReadAndxResponse.new
|
26
|
+
response.parameter_block.available = 0xffff # this field is only used for named pipes, must be -1 for all others
|
27
|
+
unless buffer.nil?
|
28
|
+
response.parameter_block.data_length = buffer.length
|
29
|
+
response.parameter_block.data_offset = response.data_block.data.abs_offset
|
30
|
+
response.data_block.data = buffer
|
31
|
+
end
|
32
|
+
response
|
33
|
+
end
|
34
|
+
|
35
|
+
def do_read_smb2(request)
|
36
|
+
local_path = get_local_path(request.file_id)
|
37
|
+
if local_path.nil?
|
38
|
+
response = RubySMB::SMB2::Packet::ErrorPacket.new
|
39
|
+
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
|
40
|
+
return response
|
41
|
+
end
|
42
|
+
|
43
|
+
raise NotImplementedError unless request.channel == SMB2::SMB2_CHANNEL_NONE
|
44
|
+
|
45
|
+
buffer = nil
|
46
|
+
local_path.open do |file|
|
47
|
+
file.seek(request.offset.snapshot)
|
48
|
+
buffer = file.read(request.read_length)
|
49
|
+
end
|
50
|
+
|
51
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/21e8b343-34e9-4fca-8d93-03dd2d3e961e
|
52
|
+
if buffer.nil? || buffer.length == 0 || buffer.length < request.min_bytes
|
53
|
+
response = SMB2::Packet::ErrorPacket.new
|
54
|
+
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_END_OF_FILE
|
55
|
+
return response
|
56
|
+
end
|
57
|
+
|
58
|
+
response = SMB2::Packet::ReadResponse.new
|
59
|
+
response.data_length = buffer.length
|
60
|
+
response.buffer = buffer
|
61
|
+
response
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|