ruby_smb 3.0.5 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +2 -3
  3. data/.github/workflows/verify.yml +1 -1
  4. data/.simplecov +1 -1
  5. data/CONTRIBUTING.md +28 -3
  6. data/README.md +8 -0
  7. data/examples/pwsh_service.rb +112 -0
  8. data/lib/ruby_smb/client/encryption.rb +16 -4
  9. data/lib/ruby_smb/client/negotiation.rb +10 -8
  10. data/lib/ruby_smb/dcerpc/request.rb +2 -0
  11. data/lib/ruby_smb/dcerpc/svcctl/create_service_w_request.rb +35 -0
  12. data/lib/ruby_smb/dcerpc/svcctl/create_service_w_response.rb +24 -0
  13. data/lib/ruby_smb/dcerpc/svcctl/delete_service_request.rb +21 -0
  14. data/lib/ruby_smb/dcerpc/svcctl/delete_service_response.rb +21 -0
  15. data/lib/ruby_smb/dcerpc/svcctl.rb +66 -5
  16. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +1 -1
  17. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +1 -1
  18. data/lib/ruby_smb/dcerpc/winreg.rb +1 -1
  19. data/lib/ruby_smb/fscc/file_information.rb +4 -0
  20. data/lib/ruby_smb/gss/provider/ntlm.rb +4 -0
  21. data/lib/ruby_smb/server/server_client/encryption.rb +66 -0
  22. data/lib/ruby_smb/server/server_client/negotiation.rb +14 -3
  23. data/lib/ruby_smb/server/server_client/session_setup.rb +18 -3
  24. data/lib/ruby_smb/server/server_client/share_io.rb +17 -0
  25. data/lib/ruby_smb/server/server_client/tree_connect.rb +40 -3
  26. data/lib/ruby_smb/server/server_client.rb +147 -37
  27. data/lib/ruby_smb/server/share/provider/disk/file_system.rb +28 -0
  28. data/lib/ruby_smb/server/share/provider/disk/processor/close.rb +42 -0
  29. data/lib/ruby_smb/server/share/provider/disk/processor/create.rb +143 -0
  30. data/lib/ruby_smb/server/share/provider/disk/processor/query.rb +359 -0
  31. data/lib/ruby_smb/server/share/provider/disk/processor/read.rb +69 -0
  32. data/lib/ruby_smb/server/share/provider/disk/processor.rb +159 -0
  33. data/lib/ruby_smb/server/share/provider/disk.rb +4 -416
  34. data/lib/ruby_smb/server/share/provider/pipe.rb +2 -2
  35. data/lib/ruby_smb/server/share/provider/processor.rb +16 -0
  36. data/lib/ruby_smb/signing.rb +18 -4
  37. data/lib/ruby_smb/smb1/bit_field/directory_access_mask.rb +1 -1
  38. data/lib/ruby_smb/smb1/bit_field/file_access_mask.rb +1 -1
  39. data/lib/ruby_smb/smb1/commands.rb +1 -0
  40. data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +11 -1
  41. data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +1 -1
  42. data/lib/ruby_smb/smb1/packet/read_andx_response.rb +5 -4
  43. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +12 -4
  44. data/lib/ruby_smb/smb1/packet/trans2/data_block.rb +9 -1
  45. data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +52 -51
  46. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +37 -37
  47. data/lib/ruby_smb/smb1/packet/trans2/find_information_level/find_file_both_directory_info.rb +48 -0
  48. data/lib/ruby_smb/smb1/packet/trans2/find_information_level.rb +28 -15
  49. data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +51 -51
  50. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +36 -36
  51. data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +40 -39
  52. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +40 -40
  53. data/lib/ruby_smb/smb1/packet/trans2/query_file_information_request.rb +60 -0
  54. data/lib/ruby_smb/smb1/packet/trans2/query_file_information_response.rb +59 -0
  55. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level/query_fs_attribute_info.rb +31 -0
  56. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level.rb +40 -0
  57. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request.rb +46 -0
  58. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response.rb +59 -0
  59. data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_basic_info.rb +23 -0
  60. data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_standard_info.rb +22 -0
  61. data/lib/ruby_smb/smb1/packet/trans2/query_information_level.rb +62 -0
  62. data/lib/ruby_smb/smb1/packet/trans2/query_path_information_request.rb +65 -0
  63. data/lib/ruby_smb/smb1/packet/trans2/query_path_information_response.rb +59 -0
  64. data/lib/ruby_smb/smb1/packet/trans2/request.rb +24 -8
  65. data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +4 -4
  66. data/lib/ruby_smb/smb1/packet/trans2/response.rb +29 -20
  67. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +42 -42
  68. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +23 -23
  69. data/lib/ruby_smb/smb1/packet/trans2/subcommands.rb +23 -5
  70. data/lib/ruby_smb/smb1/packet/trans2.rb +4 -0
  71. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +4 -1
  72. data/lib/ruby_smb/smb2/bit_field/directory_access_mask.rb +1 -1
  73. data/lib/ruby_smb/smb2/bit_field/file_access_mask.rb +1 -1
  74. data/lib/ruby_smb/smb2/negotiate_context.rb +10 -1
  75. data/lib/ruby_smb/smb2/packet/transform_header.rb +7 -7
  76. data/lib/ruby_smb/smb2.rb +1 -0
  77. data/lib/ruby_smb/version.rb +1 -1
  78. data/ruby_smb.gemspec +1 -1
  79. data/spec/lib/ruby_smb/client_spec.rb +31 -8
  80. data/spec/lib/ruby_smb/dcerpc/svcctl/create_service_w_request_spec.rb +143 -0
  81. data/spec/lib/ruby_smb/dcerpc/svcctl/create_service_w_response_spec.rb +45 -0
  82. data/spec/lib/ruby_smb/dcerpc/svcctl/delete_service_request_spec.rb +29 -0
  83. data/spec/lib/ruby_smb/dcerpc/svcctl/delete_service_response_spec.rb +29 -0
  84. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +8 -8
  85. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +1 -1
  86. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +1 -1
  87. data/spec/lib/ruby_smb/smb1/bit_field/directory_access_mask_spec.rb +4 -4
  88. data/spec/lib/ruby_smb/smb1/bit_field/file_access_mask_spec.rb +4 -4
  89. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_request_spec.rb +2 -2
  90. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +36 -2
  91. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_request_spec.rb +2 -2
  92. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +35 -1
  93. data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_request_spec.rb +74 -0
  94. data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_response_spec.rb +96 -0
  95. data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request_spec.rb +62 -0
  96. data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response_spec.rb +88 -0
  97. data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_request_spec.rb +79 -0
  98. data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_response_spec.rb +96 -0
  99. data/spec/lib/ruby_smb/smb1/packet/trans2/request_spec.rb +2 -2
  100. data/spec/lib/ruby_smb/smb1/packet/trans2/response_spec.rb +3 -3
  101. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_request_spec.rb +3 -2
  102. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb +7 -2
  103. data/spec/lib/ruby_smb/smb1/tree_spec.rb +3 -3
  104. data/spec/lib/ruby_smb/smb2/bit_field/directory_access_mask_spec.rb +4 -4
  105. data/spec/lib/ruby_smb/smb2/bit_field/file_access_mask_spec.rb +4 -4
  106. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +2 -2
  107. data/spec/spec_helper.rb +2 -3
  108. data.tar.gz.sig +0 -0
  109. metadata +48 -4
  110. metadata.gz.sig +1 -2
@@ -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