ruby_smb 3.0.6 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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