ruby_smb 3.0.6 → 3.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/examples/file_server.rb +8 -1
  4. data/examples/virtual_file_server.rb +143 -0
  5. data/lib/ruby_smb/client/encryption.rb +16 -4
  6. data/lib/ruby_smb/client/negotiation.rb +10 -8
  7. data/lib/ruby_smb/fscc/file_information/file_access_information.rb +15 -0
  8. data/lib/ruby_smb/fscc/file_information/file_alignment_information.rb +45 -0
  9. data/lib/ruby_smb/fscc/file_information/file_all_information.rb +23 -0
  10. data/lib/ruby_smb/fscc/file_information/file_basic_information.rb +20 -0
  11. data/lib/ruby_smb/fscc/file_information/file_both_directory_information.rb +3 -3
  12. data/lib/ruby_smb/fscc/file_information/file_directory_information.rb +3 -3
  13. data/lib/ruby_smb/fscc/file_information/file_ea_information.rb +1 -0
  14. data/lib/ruby_smb/fscc/file_information/file_full_directory_information.rb +3 -3
  15. data/lib/ruby_smb/fscc/file_information/file_id_both_directory_information.rb +3 -3
  16. data/lib/ruby_smb/fscc/file_information/file_id_full_directory_information.rb +3 -3
  17. data/lib/ruby_smb/fscc/file_information/file_internal_information.rb +15 -0
  18. data/lib/ruby_smb/fscc/file_information/file_mode_information.rb +29 -0
  19. data/lib/ruby_smb/fscc/file_information/file_name_information.rb +16 -0
  20. data/lib/ruby_smb/fscc/file_information/file_names_information.rb +1 -1
  21. data/lib/ruby_smb/fscc/file_information/file_normalized_name_information.rb +16 -0
  22. data/lib/ruby_smb/fscc/file_information/file_position_information.rb +15 -0
  23. data/lib/ruby_smb/fscc/file_information/file_rename_information.rb +1 -1
  24. data/lib/ruby_smb/fscc/file_information/file_standard_information.rb +20 -0
  25. data/lib/ruby_smb/fscc/file_information/file_stream_information.rb +3 -0
  26. data/lib/ruby_smb/fscc/file_information.rb +43 -6
  27. data/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information.rb +1 -0
  28. data/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information.rb +1 -0
  29. data/lib/ruby_smb/fscc/file_system_information.rb +4 -0
  30. data/lib/ruby_smb/gss/provider/ntlm.rb +20 -3
  31. data/lib/ruby_smb/gss/provider.rb +10 -1
  32. data/lib/ruby_smb/server/server_client/encryption.rb +66 -0
  33. data/lib/ruby_smb/server/server_client/negotiation.rb +14 -3
  34. data/lib/ruby_smb/server/server_client/session_setup.rb +21 -4
  35. data/lib/ruby_smb/server/server_client/share_io.rb +17 -0
  36. data/lib/ruby_smb/server/server_client/tree_connect.rb +40 -3
  37. data/lib/ruby_smb/server/server_client.rb +156 -38
  38. data/lib/ruby_smb/server/session.rb +5 -1
  39. data/lib/ruby_smb/server/share/provider/disk/file_system.rb +28 -0
  40. data/lib/ruby_smb/server/share/provider/disk/processor/close.rb +46 -0
  41. data/lib/ruby_smb/server/share/provider/disk/processor/create.rb +143 -0
  42. data/lib/ruby_smb/server/share/provider/disk/processor/query.rb +359 -0
  43. data/lib/ruby_smb/server/share/provider/disk/processor/read.rb +70 -0
  44. data/lib/ruby_smb/server/share/provider/disk/processor.rb +223 -0
  45. data/lib/ruby_smb/server/share/provider/disk.rb +12 -418
  46. data/lib/ruby_smb/server/share/provider/pipe.rb +2 -2
  47. data/lib/ruby_smb/server/share/provider/processor.rb +16 -0
  48. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_file.rb +85 -0
  49. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname.rb +196 -0
  50. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat.rb +175 -0
  51. data/lib/ruby_smb/server/share/provider/virtual_disk.rb +116 -0
  52. data/lib/ruby_smb/server/share/provider.rb +1 -0
  53. data/lib/ruby_smb/server.rb +13 -3
  54. data/lib/ruby_smb/signing.rb +18 -4
  55. data/lib/ruby_smb/smb1/commands.rb +1 -0
  56. data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +11 -1
  57. data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +1 -1
  58. data/lib/ruby_smb/smb1/packet/read_andx_response.rb +5 -4
  59. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +12 -4
  60. data/lib/ruby_smb/smb1/packet/trans2/data_block.rb +9 -1
  61. data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +52 -51
  62. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +37 -37
  63. data/lib/ruby_smb/smb1/packet/trans2/find_information_level/find_file_both_directory_info.rb +48 -0
  64. data/lib/ruby_smb/smb1/packet/trans2/find_information_level.rb +28 -15
  65. data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +51 -51
  66. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +36 -36
  67. data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +40 -39
  68. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +40 -40
  69. data/lib/ruby_smb/smb1/packet/trans2/query_file_information_request.rb +60 -0
  70. data/lib/ruby_smb/smb1/packet/trans2/query_file_information_response.rb +59 -0
  71. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level/query_fs_attribute_info.rb +31 -0
  72. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level.rb +40 -0
  73. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request.rb +46 -0
  74. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response.rb +59 -0
  75. data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_basic_info.rb +23 -0
  76. data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_standard_info.rb +22 -0
  77. data/lib/ruby_smb/smb1/packet/trans2/query_information_level.rb +62 -0
  78. data/lib/ruby_smb/smb1/packet/trans2/query_path_information_request.rb +65 -0
  79. data/lib/ruby_smb/smb1/packet/trans2/query_path_information_response.rb +59 -0
  80. data/lib/ruby_smb/smb1/packet/trans2/request.rb +24 -8
  81. data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +4 -4
  82. data/lib/ruby_smb/smb1/packet/trans2/response.rb +29 -20
  83. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +42 -42
  84. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +23 -23
  85. data/lib/ruby_smb/smb1/packet/trans2/subcommands.rb +23 -5
  86. data/lib/ruby_smb/smb1/packet/trans2.rb +4 -0
  87. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +4 -1
  88. data/lib/ruby_smb/smb2/negotiate_context.rb +10 -1
  89. data/lib/ruby_smb/smb2/packet/transform_header.rb +7 -7
  90. data/lib/ruby_smb/smb2/tree.rb +1 -0
  91. data/lib/ruby_smb/smb2.rb +1 -0
  92. data/lib/ruby_smb/version.rb +1 -1
  93. data/spec/lib/ruby_smb/client_spec.rb +31 -8
  94. data/spec/lib/ruby_smb/fscc/file_information/file_access_information_spec.rb +21 -0
  95. data/spec/lib/ruby_smb/fscc/file_information/file_alignment_information_spec.rb +21 -0
  96. data/spec/lib/ruby_smb/fscc/file_information/file_all_information_spec.rb +61 -0
  97. data/spec/lib/ruby_smb/fscc/file_information/file_basic_information_spec.rb +41 -0
  98. data/spec/lib/ruby_smb/fscc/file_information/file_both_directory_information_spec.rb +59 -10
  99. data/spec/lib/ruby_smb/fscc/file_information/file_directory_information_spec.rb +30 -12
  100. data/spec/lib/ruby_smb/fscc/file_information/file_ea_information_spec.rb +21 -0
  101. data/spec/lib/ruby_smb/fscc/file_information/file_full_directory_information_spec.rb +30 -12
  102. data/spec/lib/ruby_smb/fscc/file_information/file_id_both_directory_information_spec.rb +63 -10
  103. data/spec/lib/ruby_smb/fscc/file_information/file_id_full_directory_information_spec.rb +30 -12
  104. data/spec/lib/ruby_smb/fscc/file_information/file_internal_information_spec.rb +21 -0
  105. data/spec/lib/ruby_smb/fscc/file_information/file_mode_information_spec.rb +21 -0
  106. data/spec/lib/ruby_smb/fscc/file_information/file_name_information_spec.rb +44 -0
  107. data/spec/lib/ruby_smb/fscc/file_information/file_names_information_spec.rb +30 -12
  108. data/spec/lib/ruby_smb/fscc/file_information/file_network_open_information_spec.rb +51 -0
  109. data/spec/lib/ruby_smb/fscc/file_information/file_normalized_name_information_spec.rb +44 -0
  110. data/spec/lib/ruby_smb/fscc/file_information/file_position_information_spec.rb +21 -0
  111. data/spec/lib/ruby_smb/fscc/file_information/file_rename_information_spec.rb +1 -1
  112. data/spec/lib/ruby_smb/fscc/file_information/file_standard_information_spec.rb +41 -0
  113. data/spec/lib/ruby_smb/fscc/file_information/file_stream_information_spec.rb +51 -0
  114. data/spec/lib/ruby_smb/fscc/file_information_spec.rb +14 -0
  115. data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information_spec.rb +46 -0
  116. data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information_spec.rb +51 -0
  117. data/spec/lib/ruby_smb/fscc/file_system_information_spec.rb +14 -0
  118. data/spec/lib/ruby_smb/server/server_client_spec.rb +15 -0
  119. data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname_spec.rb +581 -0
  120. data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat_spec.rb +207 -0
  121. data/spec/lib/ruby_smb/server/share/provider/virtual_disk_spec.rb +122 -0
  122. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_request_spec.rb +2 -2
  123. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +36 -2
  124. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_request_spec.rb +2 -2
  125. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +35 -1
  126. data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_request_spec.rb +74 -0
  127. data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_response_spec.rb +96 -0
  128. data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request_spec.rb +62 -0
  129. data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response_spec.rb +88 -0
  130. data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_request_spec.rb +79 -0
  131. data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_response_spec.rb +96 -0
  132. data/spec/lib/ruby_smb/smb1/packet/trans2/request_spec.rb +2 -2
  133. data/spec/lib/ruby_smb/smb1/packet/trans2/response_spec.rb +3 -3
  134. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_request_spec.rb +3 -2
  135. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb +7 -2
  136. data/spec/lib/ruby_smb/smb1/tree_spec.rb +3 -3
  137. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +2 -2
  138. data.tar.gz.sig +0 -0
  139. metadata +88 -2
  140. metadata.gz.sig +0 -0
@@ -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::FileNormalizedNameInformation.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} (#{Fscc::FileSystemInformation.name(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,70 @@
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
+ handle = @handles[request.parameter_block.fid]
13
+
14
+ if handle.nil? || handle.file.nil?
15
+ response = SMB1::Packet::EmptyPacket.new
16
+ response.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE
17
+ return response
18
+ end
19
+
20
+ handle.file.seek(request.parameter_block.offset.snapshot)
21
+ buffer = handle.file.read(request.parameter_block.max_count_of_bytes_to_return.snapshot)
22
+
23
+ response = SMB1::Packet::ReadAndxResponse.new
24
+ response.parameter_block.available = 0xffff # this field is only used for named pipes, must be -1 for all others
25
+ unless buffer.nil?
26
+ response.parameter_block.data_length = buffer.length
27
+ response.parameter_block.data_offset = response.data_block.data.abs_offset
28
+ response.data_block.data = buffer
29
+ end
30
+ response
31
+ end
32
+
33
+ def do_read_smb2(request)
34
+ handle = @handles[request.file_id.to_binary_s]
35
+ if handle.nil?
36
+ response = RubySMB::SMB2::Packet::ErrorPacket.new
37
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
38
+ return response
39
+ end
40
+
41
+ if handle.file.nil?
42
+ response = RubySMB::SMB2::Packet::ErrorPacket.new
43
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE
44
+ return response
45
+ end
46
+
47
+ raise NotImplementedError unless request.channel == SMB2::SMB2_CHANNEL_NONE
48
+
49
+ handle.file.seek(request.offset.snapshot)
50
+ buffer = handle.file.read(request.read_length)
51
+
52
+ # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/21e8b343-34e9-4fca-8d93-03dd2d3e961e
53
+ if buffer.nil? || buffer.length == 0 || buffer.length < request.min_bytes
54
+ response = SMB2::Packet::ErrorPacket.new
55
+ response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_END_OF_FILE
56
+ return response
57
+ end
58
+
59
+ response = SMB2::Packet::ReadResponse.new
60
+ response.data_length = buffer.length
61
+ response.buffer = buffer
62
+ response
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,223 @@
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
+ require 'ruby_smb/server/share/provider/disk/processor/close'
11
+ require 'ruby_smb/server/share/provider/disk/processor/create'
12
+ require 'ruby_smb/server/share/provider/disk/processor/query'
13
+ require 'ruby_smb/server/share/provider/disk/processor/read'
14
+
15
+ include RubySMB::Server::Share::Provider::Disk::Processor::Close
16
+ include RubySMB::Server::Share::Provider::Disk::Processor::Create
17
+ include RubySMB::Server::Share::Provider::Disk::Processor::Query
18
+ include RubySMB::Server::Share::Provider::Disk::Processor::Read
19
+
20
+ Handle = Struct.new(:remote_path, :local_path, :durable?, :file)
21
+ def initialize(provider, server_client, session)
22
+ super
23
+ @handles = {}
24
+ @query_directory_context = {}
25
+ end
26
+
27
+ def maximal_access(path=nil)
28
+ RubySMB::SMB2::BitField::FileAccessMask.new(
29
+ read_attr: 1,
30
+ read_data: 1
31
+ )
32
+ end
33
+
34
+ # Build an access mask bit field for the specified path. The return type is a DirectoryAccessMask if path
35
+ # is a directory, otherwise it's a FileAccessMask.
36
+ #
37
+ # @param Pathname path the path to build an access mask for
38
+ # @return [DirectoryAccessMask, FileAccessMask] the access mask
39
+ def smb2_access_mask(path)
40
+ # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/b3af3aaf-9271-4419-b326-eba0341df7d2
41
+ if path.directory?
42
+ am = SMB2::BitField::DirectoryAccessMask.new
43
+ am.traverse = true
44
+ am.list = true
45
+ else
46
+ am = SMB2::BitField::FileAccessMask.new
47
+ am.read_data = true
48
+ end
49
+ am.read_attr = true
50
+ am
51
+ end
52
+
53
+ private
54
+
55
+ def build_fscc_file_attributes(path)
56
+ file_attributes = Fscc::FileAttributes.new
57
+ if path.file?
58
+ file_attributes.normal = 1
59
+ elsif path.directory?
60
+ file_attributes.directory = 1
61
+ end
62
+ file_attributes
63
+ end
64
+
65
+ def build_fscc_file_information(path, info_class, rename: nil)
66
+ case info_class
67
+ when Fscc::FileInformation::FILE_ACCESS_INFORMATION
68
+ info = Fscc::FileInformation::FileAccessInformation.new
69
+ # smb2_access_mask returns back either file or directory access mask depending on what path is,
70
+ # FileAccessInformation however isn't defined to account for this context so set it from the binary
71
+ # value
72
+ info.access_flags.read(smb2_access_mask(path).to_binary_s)
73
+ when Fscc::FileInformation::FILE_ALIGNMENT_INFORMATION
74
+ info = Fscc::FileInformation::FileAlignmentInformation.new
75
+ when Fscc::FileInformation::FILE_ALL_INFORMATION
76
+ info = Fscc::FileInformation::FileAllInformation.new
77
+ info.basic_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_BASIC_INFORMATION, rename: rename)
78
+ info.standard_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_STANDARD_INFORMATION, rename: rename)
79
+ info.internal_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_INTERNAL_INFORMATION, rename: rename)
80
+ info.ea_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_EA_INFORMATION, rename: rename)
81
+ info.access_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_ACCESS_INFORMATION, rename: rename)
82
+ info.position_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_POSITION_INFORMATION, rename: rename)
83
+ info.mode_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_MODE_INFORMATION, rename: rename)
84
+ info.alignment_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_ALIGNMENT_INFORMATION, rename: rename)
85
+ info.name_information = build_fscc_file_information(path, Fscc::FileInformation::FILE_NAME_INFORMATION, rename: rename)
86
+ when Fscc::FileInformation::FILE_BASIC_INFORMATION
87
+ info = Fscc::FileInformation::FileBasicInformation.new
88
+ set_common_timestamps(info, path)
89
+ info.file_attributes = build_fscc_file_attributes(path)
90
+ when Fscc::FileInformation::FILE_EA_INFORMATION
91
+ info = Fscc::FileInformation::FileEaInformation.new
92
+ when Fscc::FileInformation::FILE_FULL_DIRECTORY_INFORMATION
93
+ info = Fscc::FileInformation::FileFullDirectoryInformation.new
94
+ set_common_info(info, path)
95
+ info.file_name = rename || path.basename.to_s
96
+ when Fscc::FileInformation::FILE_ID_BOTH_DIRECTORY_INFORMATION
97
+ info = Fscc::FileInformation::FileIdBothDirectoryInformation.new
98
+ set_common_info(info, path)
99
+ info.file_name = rename || path.basename.to_s
100
+ when Fscc::FileInformation::FILE_ID_FULL_DIRECTORY_INFORMATION
101
+ info = Fscc::FileInformation::FileIdFullDirectoryInformation.new
102
+ set_common_info(info, path)
103
+ info.file_name = rename || path.basename.to_s
104
+ when Fscc::FileInformation::FILE_INTERNAL_INFORMATION
105
+ info = Fscc::FileInformation::FileInternalInformation.new
106
+ info.file_id = Zlib::crc32(path.to_s)
107
+ when Fscc::FileInformation::FILE_MODE_INFORMATION
108
+ info = Fscc::FileInformation::FileModeInformation.new
109
+ when Fscc::FileInformation::FILE_NAME_INFORMATION
110
+ info = Fscc::FileInformation::FileNameInformation.new
111
+ info.file_name = rename || path.basename.to_s
112
+ when Fscc::FileInformation::FILE_NETWORK_OPEN_INFORMATION
113
+ info = Fscc::FileInformation::FileNetworkOpenInformation.new
114
+ set_common_info(info, path)
115
+ when Fscc::FileInformation::FILE_POSITION_INFORMATION
116
+ info = Fscc::FileInformation::FilePositionInformation.new
117
+ info.current_byte_offset = path.size
118
+ when Fscc::FileInformation::FILE_STANDARD_INFORMATION
119
+ info = Fscc::FileInformation::FileStandardInformation.new
120
+ info.allocation_size = get_allocation_size(path)
121
+ info.end_of_file = path.size
122
+ info.directory = path.directory? ? 1 : 0
123
+ when Fscc::FileInformation::FILE_STREAM_INFORMATION
124
+ unless path.file?
125
+ raise NotImplementedError, 'Can only generate FILE_STREAM_INFORMATION for files'
126
+ end
127
+
128
+ info = Fscc::FileInformation::FileStreamInformation.new(
129
+ stream_size: path.size,
130
+ stream_allocation_size: get_allocation_size(path),
131
+ stream_name: '::$DATA'
132
+ )
133
+ else
134
+ logger.warn("Unsupported FSCC file information class: #{info_class} (#{Fscc::FileInformation.name(info_class)})")
135
+ raise NotImplementedError
136
+ end
137
+
138
+ # some have a next offset field that needs to be set accordingly
139
+ if info.respond_to?(:next_offset)
140
+ align = 8
141
+ info.next_offset = info.num_bytes + ((align - info.num_bytes % align) % align)
142
+ end
143
+
144
+ info
145
+ end
146
+
147
+ def get_allocation_size(path)
148
+ (path.size + (4095 - (path.size + 4095) % 4096))
149
+ end
150
+
151
+ def get_local_path(path)
152
+ case path
153
+ # SMB1 uses uint16_t file IDs
154
+ when ::BinData::Uint16le
155
+ local_path = @handles[path]&.local_path
156
+ # SMB2 uses a compound field for file IDs, so convert it to the binary rep and use that as the key
157
+ when Field::Smb2Fileid
158
+ local_path = @handles[path.to_binary_s]&.local_path
159
+ when ::String
160
+ path = path.encode.gsub(/\/|\\/, File::SEPARATOR)
161
+ path = path.delete_prefix(File::SEPARATOR)
162
+ local_path = (provider.path + path.encode).cleanpath
163
+ unless local_path == provider.path || local_path.to_s.start_with?(provider.path.to_s.delete_suffix(File::SEPARATOR) + File::SEPARATOR)
164
+ raise RuntimeError, "Directory traversal detected to: #{local_path}"
165
+ end
166
+ else
167
+ raise NotImplementedError, "Can not get the local path for: #{path.inspect}, type: #{path.class.inspect}"
168
+ end
169
+
170
+ local_path
171
+ end
172
+
173
+ # A bunch of structures have these common fields with the same meaning, so set them all here
174
+ def set_common_info(info, path)
175
+ set_common_timestamps(info, path)
176
+ if path.file?
177
+ info.end_of_file = path.size
178
+ info.allocation_size = get_allocation_size(path)
179
+ end
180
+ info.file_attributes = build_fscc_file_attributes(path)
181
+ end
182
+
183
+ def set_common_timestamps(info, path)
184
+ begin
185
+ info.create_time = path.birthtime
186
+ rescue NotImplementedError
187
+ logger.warn("The file system does not support #birthtime for #{path}")
188
+ end
189
+
190
+ info.last_access = path.atime
191
+ info.last_write = path.mtime
192
+ info.last_change = path.ctime
193
+ end
194
+
195
+ # Turn a wildcard expression into a regex. Not all wildcard
196
+ # characters are supported. Wildcards that can not be converted will
197
+ # raise a NotImplementedError.
198
+ #
199
+ # @param [String] wildcard The wildcard expression to convert.
200
+ # @return [Regexp] The converted expression.
201
+ # @raise [NotImplementedError] Raised when the wildcard can not be
202
+ # converted.
203
+ def wildcard_to_regex(wildcard)
204
+ return Regexp.new('.*') if ['*.*', ''].include?(wildcard)
205
+
206
+ if wildcard.each_char.any? { |c| c == '<' || c == '>' }
207
+ # the < > wildcard operators are not supported
208
+ raise NotImplementedError, 'Unsupported wild card characters'
209
+ end
210
+
211
+ wildcard = Regexp.escape(wildcard)
212
+ wildcard = wildcard.gsub(/(\\\?)+$/) { |match| ".{0,#{match.length / 2}}"}
213
+ wildcard = wildcard.gsub('\?', '.')
214
+ wildcard = wildcard.gsub('\*', '.*')
215
+ wildcard = wildcard.gsub('"', '\.')
216
+ Regexp.new('^' + wildcard + '$')
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end