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,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