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,159 @@
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
+ require 'ruby_smb/server/share/provider/disk/processor/close'
10
+ require 'ruby_smb/server/share/provider/disk/processor/create'
11
+ require 'ruby_smb/server/share/provider/disk/processor/query'
12
+ require 'ruby_smb/server/share/provider/disk/processor/read'
13
+
14
+ include RubySMB::Server::Share::Provider::Disk::Processor::Close
15
+ include RubySMB::Server::Share::Provider::Disk::Processor::Create
16
+ include RubySMB::Server::Share::Provider::Disk::Processor::Query
17
+ include RubySMB::Server::Share::Provider::Disk::Processor::Read
18
+
19
+ Handle = Struct.new(:remote_path, :local_path, :durable?)
20
+ def initialize(provider, server_client, session)
21
+ super
22
+ @handles = {}
23
+ @query_directory_context = {}
24
+ end
25
+
26
+ def maximal_access(path=nil)
27
+ RubySMB::SMB2::BitField::FileAccessMask.new(
28
+ read_attr: 1,
29
+ read_data: 1
30
+ )
31
+ end
32
+
33
+ private
34
+
35
+ def build_fscc_file_attributes(path)
36
+ file_attributes = Fscc::FileAttributes.new
37
+ if path.file?
38
+ file_attributes.normal = 1
39
+ elsif path.directory?
40
+ file_attributes.directory = 1
41
+ end
42
+ file_attributes
43
+ end
44
+
45
+ def build_fscc_file_information(path, info_class, rename: nil)
46
+ case info_class
47
+ when Fscc::FileInformation::FILE_EA_INFORMATION
48
+ info = Fscc::FileInformation::FileEaInformation.new
49
+ when Fscc::FileInformation::FILE_FULL_DIRECTORY_INFORMATION
50
+ info = Fscc::FileInformation::FileFullDirectoryInformation.new
51
+ set_common_info(info, path)
52
+ info.file_name = rename || path.basename.to_s
53
+ when Fscc::FileInformation::FILE_ID_BOTH_DIRECTORY_INFORMATION
54
+ info = Fscc::FileInformation::FileIdBothDirectoryInformation.new
55
+ set_common_info(info, path)
56
+ info.file_name = rename || path.basename.to_s
57
+ when Fscc::FileInformation::FILE_NETWORK_OPEN_INFORMATION
58
+ info = Fscc::FileInformation::FileNetworkOpenInformation.new
59
+ set_common_info(info, path)
60
+ when Fscc::FileInformation::FILE_STREAM_INFORMATION
61
+ unless path.file?
62
+ raise NotImplementedError, 'Can only generate FILE_STREAM_INFORMATION for files'
63
+ end
64
+
65
+ info = Fscc::FileInformation::FileStreamInformation.new(
66
+ stream_size: path.size,
67
+ stream_allocation_size: get_allocation_size(path),
68
+ stream_name: '::$DATA'
69
+ )
70
+ else
71
+ raise NotImplementedError, "Unsupported FSCC file information class: #{info_class} (#{Fscc::FileInformation.name(info_class)})"
72
+ end
73
+
74
+ # some have a next offset field that needs to be set accordingly
75
+ if info.respond_to?(:next_offset)
76
+ align = 8
77
+ info.next_offset = info.num_bytes + ((align - info.num_bytes % align) % align)
78
+ end
79
+
80
+ info
81
+ end
82
+
83
+ def get_allocation_size(path)
84
+ (path.size + (4095 - (path.size + 4095) % 4096))
85
+ end
86
+
87
+ def get_local_path(path)
88
+ case path
89
+ # SMB1 uses uint16_t file IDs
90
+ when ::BinData::Uint16le
91
+ local_path = @handles[path]&.local_path
92
+ # SMB2 uses a compound field for file IDs, so convert it to the binary rep and use that as the key
93
+ when Field::Smb2Fileid
94
+ local_path = @handles[path.to_binary_s]&.local_path
95
+ when ::String
96
+ path = path.encode.gsub(/\/|\\/, File::SEPARATOR)
97
+ path = path.delete_prefix(File::SEPARATOR)
98
+ local_path = (provider.path + path.encode).cleanpath
99
+ unless local_path == provider.path || local_path.to_s.start_with?(provider.path.to_s + '/')
100
+ raise RuntimeError, "Directory traversal detected to: #{local_path}"
101
+ end
102
+ else
103
+ raise NotImplementedError, "Can not get the local path for: #{path.inspect}, type: #{path.class.inspect}"
104
+ end
105
+
106
+ local_path
107
+ end
108
+
109
+ # A bunch of structures have these common fields with the same meaning, so set them all here
110
+ def set_common_info(info, path)
111
+ set_common_timestamps(info, path)
112
+ if path.file?
113
+ info.end_of_file = path.size
114
+ info.allocation_size = get_allocation_size(path)
115
+ end
116
+ info.file_attributes = build_fscc_file_attributes(path)
117
+ end
118
+
119
+ def set_common_timestamps(info, path)
120
+ begin
121
+ info.create_time = path.birthtime
122
+ rescue NotImplementedError
123
+ logger.warn("The file system does not support #birthtime for #{path}")
124
+ end
125
+
126
+ info.last_access = path.atime
127
+ info.last_write = path.mtime
128
+ info.last_change = path.ctime
129
+ end
130
+
131
+ # Turn a wildcard expression into a regex. Not all wildcard
132
+ # characters are supported. Wildcards that can not be converted will
133
+ # raise a NotImplementedError.
134
+ #
135
+ # @param [String] wildcard The wildcard expression to convert.
136
+ # @return [Regexp] The converted expression.
137
+ # @raise [NotImplementedError] Raised when the wildcard can not be
138
+ # converted.
139
+ def wildcard_to_regex(wildcard)
140
+ return Regexp.new('.*') if ['*.*', ''].include?(wildcard)
141
+
142
+ if wildcard.each_char.any? { |c| c == '<' || c == '>' }
143
+ # the < > wildcard operators are not supported
144
+ raise NotImplementedError, 'Unsupported wild card characters'
145
+ end
146
+
147
+ wildcard = Regexp.escape(wildcard)
148
+ wildcard = wildcard.gsub(/(\\\?)+$/) { |match| ".{0,#{match.length / 2}}"}
149
+ wildcard = wildcard.gsub('\?', '.')
150
+ wildcard = wildcard.gsub('\*', '.*')
151
+ wildcard = wildcard.gsub('"', '\.')
152
+ Regexp.new('^' + wildcard + '$')
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -1,5 +1,5 @@
1
- require 'zlib'
2
- require 'ruby_smb/server/share/provider/processor'
1
+ require 'ruby_smb/server/share/provider/disk/file_system'
2
+ require 'ruby_smb/server/share/provider/disk/processor'
3
3
 
4
4
  module RubySMB
5
5
  class Server
@@ -7,420 +7,8 @@ module RubySMB
7
7
  module Provider
8
8
  class Disk < Base
9
9
  TYPE = TYPE_DISK
10
- class Processor < Processor::Base
11
- Handle = Struct.new(:remote_path, :local_path, :durable?)
12
- def initialize(provider, server_client, session)
13
- super
14
- @handles = {}
15
- @query_directory_context = {}
16
- end
17
-
18
- def maximal_access(path=nil)
19
- RubySMB::SMB2::BitField::FileAccessMask.new(
20
- read_attr: 1,
21
- read_data: 1
22
- )
23
- end
24
-
25
- def do_close_smb2(request)
26
- local_path = get_local_path(request.file_id)
27
- if local_path.nil?
28
- response = RubySMB::SMB2::Packet::ErrorPacket.new
29
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
30
- return response
31
- end
32
-
33
- @handles.delete(request.file_id.to_binary_s)
34
- response = RubySMB::SMB2::Packet::CloseResponse.new
35
- set_common_info(response, local_path)
36
- response.flags = 1
37
- response
38
- end
39
-
40
- def do_create_smb2(request)
41
- unless request.create_disposition == RubySMB::Dispositions::FILE_OPEN
42
- logger.warn("Can not handle CREATE request for disposition: #{request.create_disposition}")
43
- raise NotImplementedError
44
- end
45
-
46
- # process the delayed io fields
47
- request.name.read_now!
48
- unless request.contexts_offset == 0
49
- request.contexts.read_now!
50
- request.contexts.each do |context|
51
- context.name.read_now!
52
- context.data.read_now!
53
- end
54
- end
55
-
56
- path = request.name.snapshot
57
- path = path.encode.gsub('\\', File::SEPARATOR)
58
- local_path = get_local_path(path)
59
- unless local_path && (local_path.file? || local_path.directory?)
60
- logger.warn("Requested path does not exist: #{local_path}")
61
- response = RubySMB::SMB2::Packet::ErrorPacket.new
62
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND
63
- return response
64
- end
65
-
66
- durable = false
67
- response = RubySMB::SMB2::Packet::CreateResponse.new
68
- response.create_action = RubySMB::CreateActions::FILE_OPENED
69
- set_common_info(response, local_path)
70
- response.file_id.persistent = Zlib::crc32(path)
71
- response.file_id.volatile = rand(0xffffffff)
72
-
73
- request.contexts.each do |req_ctx|
74
- case req_ctx.name
75
- when SMB2::CreateContext::CREATE_DURABLE_HANDLE
76
- # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/9adbc354-5fad-40e7-9a62-4a4b6c1ff8a0
77
- next if request.contexts.any? { |ctx| ctx.name == SMB2::CreateContext::CREATE_DURABLE_HANDLE_RECONNECT }
78
-
79
- if request.contexts.any? { |ctx| [ SMB2::CreateContext::CREATE_DURABLE_HANDLE_V2, SMB2::CreateContext::CREATE_DURABLE_HANDLE_RECONNECT_v2 ].include?(ctx.name) }
80
- response = RubySMB::SMB2::Packet::ErrorPacket.new
81
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER
82
- return response
83
- end
84
-
85
- durable = true
86
- res_ctx = SMB2::CreateContext::CreateDurableHandleResponse.new
87
- when SMB2::CreateContext::CREATE_DURABLE_HANDLE_V2
88
- # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/33e6800a-adf5-4221-af27-7e089b9e81d1
89
- 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) }
90
- response = RubySMB::SMB2::Packet::ErrorPacket.new
91
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER
92
- return response
93
- end
94
-
95
- durable = true
96
- res_ctx = SMB2::CreateContext::CreateDurableHandleV2Response.new(
97
- timeout: 1000,
98
- flags: req_ctx.data.flags
99
- )
100
- when SMB2::CreateContext::CREATE_QUERY_MAXIMAL_ACCESS
101
- res_ctx = SMB2::CreateContext::CreateQueryMaximalAccessResponse.new(
102
- maximal_access: maximal_access(path)
103
- )
104
- when SMB2::CreateContext::CREATE_QUERY_ON_DISK_ID
105
- res_ctx = SMB2::CreateContext::CreateQueryOnDiskIdResponse.new(
106
- disk_file_id: local_path.stat.ino,
107
- volume_id: local_path.stat.dev
108
- )
109
- else
110
- logger.warn("Can not handle CREATE context: #{req_ctx.name}")
111
- next
112
- end
113
-
114
- response.contexts << SMB2::CreateContext::CreateContextResponse.new(name: res_ctx.class::NAME, data: res_ctx)
115
- end
116
-
117
- if response.contexts.length > 0
118
- # fixup the offsets
119
- response.contexts[0...-1].each do |ctx|
120
- ctx.next_offset = ctx.num_bytes
121
- end
122
- response.contexts[-1].next_offset = 0
123
- response.contexts_offset = response.buffer.abs_offset
124
- response.contexts_length = response.buffer.num_bytes
125
- else
126
- response.contexts_offset = 0
127
- response.contexts_length = 0
128
- end
129
-
130
- @handles[response.file_id.to_binary_s] = Handle.new(path, local_path, durable)
131
- response
132
- end
133
-
134
- def do_query_directory_smb2(request)
135
- local_path = get_local_path(request.file_id)
136
- if local_path.nil?
137
- response = RubySMB::SMB2::Packet::ErrorPacket.new
138
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
139
- return response
140
- end
141
-
142
- # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/29dfcc9b-3aec-406b-abb5-0b4fe96712e2
143
- info_class = request.file_information_class.snapshot
144
- begin
145
- # probe #build_info to see if it supports the requested info class
146
- build_info(Pathname.new(__FILE__), info_class)
147
- rescue NotImplementedError
148
- logger.warn("Can not handle QUERY_DIRECTORY request for class: #{info_class}")
149
- raise
150
- end
151
-
152
- unless local_path.directory?
153
- response = SMB2::Packet::ErrorPacket.new
154
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER
155
- return response
156
- end
157
-
158
- search_pattern = request.name.snapshot.dup.encode
159
- begin
160
- search_regex = wildcard_to_regex(search_pattern)
161
- rescue NotImplementedError
162
- logger.warn("Can not handle QUERY_DIRECTORY wildcard pattern: #{search_pattern}")
163
- raise
164
- end
165
-
166
- return_single = request.flags.return_single == 1
167
-
168
- align = 8
169
- infos = []
170
- total_size = 0
171
-
172
- if @query_directory_context[request.file_id.to_binary_s].nil? || request.flags.reopen == 1 || request.flags.restart_scans == 1
173
- dirents = local_path.children.sort.to_a
174
- dirents.unshift(local_path.parent) unless local_path.parent == local_path
175
- dirents.unshift(local_path)
176
- @query_directory_context[request.file_id.to_binary_s] = dirents
177
- else
178
- dirents = @query_directory_context[request.file_id.to_binary_s]
179
- end
180
-
181
- while dirents.length > 0
182
- dirent = dirents.shift
183
- next unless dirent.file? || dirent.directory? # filter out everything but files and directories
184
-
185
- case dirent
186
- when local_path
187
- dirent_name = '.'
188
- when local_path.parent
189
- dirent_name = '..'
190
- else
191
- dirent_name = dirent.basename.to_s
192
- end
193
- next unless search_regex.match?(dirent_name)
194
-
195
- info = build_info(dirent, info_class, rename: dirent_name)
196
- info_size = info.num_bytes + ((align - info.num_bytes % align) % align)
197
- if total_size + info_size > request.output_length
198
- dirents.unshift(dirent) # no space left for this one so put it back
199
- break
200
- end
201
-
202
- infos << info
203
- total_size += info_size
204
- break if return_single
205
- end
206
-
207
- if infos.length == 0
208
- response = SMB2::Packet::QueryDirectoryResponse.new
209
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NO_MORE_FILES
210
- return response
211
- end
212
-
213
- response = SMB2::Packet::QueryDirectoryResponse.new
214
- infos.last.next_offset = 0 if infos.last
215
- buffer = ""
216
- infos.each do |info|
217
- info = info.to_binary_s
218
- buffer << info + "\x00".b * ((align - info.length % align) % align)
219
- end
220
- response.buffer = buffer
221
- response
222
- end
223
-
224
- def do_query_info_smb2(request)
225
- local_path = get_local_path(request.file_id)
226
- if local_path.nil?
227
- response = RubySMB::SMB2::Packet::ErrorPacket.new
228
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
229
- return response
230
- end
231
-
232
- case request.info_type
233
- when SMB2::SMB2_INFO_FILE
234
- info = query_info_smb2_file(request, local_path)
235
- when SMB2::SMB2_INFO_FILESYSTEM
236
- info = query_info_smb2_filesystem(request, local_path)
237
- else
238
- logger.warn("Can not handle QUERY_INFO request for type: #{request.info_type}, class: #{request.file_information_class}")
239
- raise NotImplementedError
240
- end
241
-
242
- response = SMB2::Packet::QueryInfoResponse.new
243
- response.buffer = info.to_binary_s
244
- response
245
- end
246
-
247
- def do_read_smb2(request)
248
- local_path = get_local_path(request.file_id)
249
- if local_path.nil?
250
- response = RubySMB::SMB2::Packet::ErrorPacket.new
251
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
252
- return response
253
- end
254
-
255
- raise NotImplementedError unless request.channel == SMB2::SMB2_CHANNEL_NONE
256
-
257
- buffer = nil
258
- local_path.open do |file|
259
- file.seek(request.offset.snapshot)
260
- buffer = file.read(request.read_length)
261
- end
262
-
263
- # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/21e8b343-34e9-4fca-8d93-03dd2d3e961e
264
- if buffer.nil? || buffer.length == 0 || buffer.length < request.min_bytes
265
- response = SMB2::Packet::ErrorPacket.new
266
- response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_END_OF_FILE
267
- return response
268
- end
269
-
270
- response = SMB2::Packet::ReadResponse.new
271
- response.data_length = buffer.length
272
- response.buffer = buffer
273
- response
274
- end
275
-
276
- private
277
-
278
- def build_file_attributes(path)
279
- file_attributes = Fscc::FileAttributes.new
280
- if path.file?
281
- file_attributes.normal = 1
282
- elsif path.directory?
283
- file_attributes.directory = 1
284
- end
285
- file_attributes
286
- end
287
-
288
- def build_info(path, info_class, rename: nil)
289
- case info_class
290
- when Fscc::FileInformation::FILE_ID_BOTH_DIRECTORY_INFORMATION
291
- info = Fscc::FileInformation::FileIdBothDirectoryInformation.new
292
- set_common_info(info, path)
293
- info.file_name = rename || path.basename.to_s
294
- when Fscc::FileInformation::FILE_FULL_DIRECTORY_INFORMATION
295
- info = Fscc::FileInformation::FileFullDirectoryInformation.new
296
- set_common_info(info, path)
297
- info.file_name = rename || path.basename.to_s
298
- else
299
- raise NotImplementedError, "unsupported info class: #{info_class}"
300
- end
301
-
302
- align = 8
303
- info.next_offset = info.num_bytes + ((align - info.num_bytes % align) % align)
304
- info
305
- end
306
-
307
- def get_allocation_size(path)
308
- (path.size + (4095 - (path.size + 4095) % 4096))
309
- end
310
-
311
- def get_local_path(path)
312
- case path
313
- when Field::Smb2Fileid
314
- local_path = @handles[path.to_binary_s]&.local_path
315
- when ::String
316
- local_path = (provider.path + path.encode).cleanpath
317
- # TODO: report / handle directory traversal issues more robustly
318
- raise RuntimeError unless local_path == provider.path || local_path.to_s.start_with?(provider.path.to_s + '/')
319
- else
320
- raise NotImplementedError, "Can not get the local path for: #{path.inspect}"
321
- end
322
-
323
- local_path
324
- end
325
-
326
- def query_info_smb2_file(request, local_path)
327
- raise ArgumentError unless request.info_type == SMB2::SMB2_INFO_FILE
328
-
329
- case request.file_information_class
330
- when Fscc::FileInformation::FILE_EA_INFORMATION
331
- info = Fscc::FileInformation::FileEaInformation.new
332
- when Fscc::FileInformation::FILE_NETWORK_OPEN_INFORMATION
333
- info = Fscc::FileInformation::FileNetworkOpenInformation.new
334
- set_common_info(info, local_path)
335
- when Fscc::FileInformation::FILE_NORMALIZED_NAME_INFORMATION
336
- info = Fscc::FileInformation::FileNameInformation.new(file_name: @handles[request.file_id.to_binary_s].remote_path)
337
- when Fscc::FileInformation::FILE_STREAM_INFORMATION
338
- raise NotImplementedError unless local_path.file?
339
-
340
- info = Fscc::FileInformation::FileStreamInformation.new(
341
- stream_size: local_path.size,
342
- stream_allocation_size: get_allocation_size(local_path),
343
- stream_name: '::$DATA'
344
- )
345
- else
346
- logger.warn("Can not handle QUERY_INFO request for type: #{request.info_type}, class: #{request.file_information_class}")
347
- raise NotImplementedError
348
- end
349
-
350
- info
351
- end
352
-
353
- def query_info_smb2_filesystem(request, local_path)
354
- raise ArgumentError unless request.info_type == SMB2::SMB2_INFO_FILESYSTEM
355
-
356
- case request.file_information_class
357
- when Fscc::FileSystemInformation::FILE_FS_ATTRIBUTE_INFORMATION
358
- # emulate NTFS just like Samba does
359
- info = Fscc::FileSystemInformation::FileFsAttributeInformation.new(
360
- file_system_attributes: {
361
- file_case_sensitive_search: 1,
362
- file_case_preserved_names: 1,
363
- file_unicode_on_disk: 1,
364
- file_supports_object_ids: 1,
365
- },
366
- maximum_component_name_length: 255,
367
- file_system_name: 'NTFS'
368
- )
369
- when Fscc::FileSystemInformation::FILE_FS_VOLUME_INFORMATION
370
- info = Fscc::FileSystemInformation::FileFsVolumeInformation.new(
371
- volume_serial_number: provider.path.stat.ino,
372
- volume_label: provider.name
373
- )
374
- else
375
- logger.warn("Can not handle QUERY_INFO request for type: #{request.info_type}, class: #{request.file_information_class}")
376
- raise NotImplementedError
377
- end
378
-
379
- info
380
- end
381
-
382
- # A bunch of structures have these common fields with the same meaning, so set them all here
383
- def set_common_info(info, path)
384
- begin
385
- info.create_time = path.birthtime
386
- rescue NotImplementedError
387
- logger.warn("The file system does not support #birthtime for #{path}")
388
- end
389
-
390
- info.last_access = path.atime
391
- info.last_write = path.mtime
392
- info.last_change = path.ctime
393
- if path.file?
394
- info.end_of_file = path.size
395
- info.allocation_size = get_allocation_size(path)
396
- end
397
- info.file_attributes = build_file_attributes(path)
398
- end
399
-
400
- # Turn a wildcard expression into a regex. Not all wildcard
401
- # characters are supported. Wildcards that can not be converted will
402
- # raise a NotImplementedError.
403
- #
404
- # @param [String] wildcard The wildcard expression to convert.
405
- # @return [Regexp] The converted expression.
406
- # @raise [NotImplementedError] Raised when the wildcard can not be
407
- # converted.
408
- def wildcard_to_regex(wildcard)
409
- return Regexp.new('.*') if ['*.*', ''].include?(wildcard)
410
-
411
- if wildcard.each_char.any? { |c| c == '<' || c == '>' }
412
- # the < > wildcard operators are not supported
413
- raise NotImplementedError
414
- end
415
-
416
- wildcard = Regexp.escape(wildcard)
417
- wildcard = wildcard.gsub(/(\\\?)+$/) { |match| ".{0,#{match.length / 2}}"}
418
- wildcard = wildcard.gsub('\?', '.')
419
- wildcard = wildcard.gsub('\*', '.*')
420
- wildcard = wildcard.gsub('"', '\.')
421
- Regexp.new('^' + wildcard + '$')
422
- end
423
- end
10
+ # emulate NTFS just like Samba does
11
+ FILE_SYSTEM = FileSystem::NTFS
424
12
 
425
13
  def initialize(name, path)
426
14
  path = Pathname.new(File.expand_path(path))
@@ -6,12 +6,12 @@ module RubySMB
6
6
  module Provider
7
7
  class Pipe < Base
8
8
  TYPE = TYPE_PIPE
9
- class Processor < Processor::Base
9
+ class Processor < Provider::Processor::Base
10
10
  end
11
11
  end
12
12
 
13
13
  class IpcPipe < Pipe
14
- class Processor < Processor::Base
14
+ class Processor < Provider::Processor::Base
15
15
  def maximal_access(path=nil)
16
16
  RubySMB::SMB2::BitField::DirectoryAccessMask.read([0x001f00a9].pack('V'))
17
17
  end
@@ -25,6 +25,22 @@ module RubySMB
25
25
  def disconnect!
26
26
  end
27
27
 
28
+ def do_close_smb1(request)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ def do_nt_create_andx_smb1(request)
33
+ raise NotImplementedError
34
+ end
35
+
36
+ def do_read_andx_smb1(request)
37
+ raise NotImplementedError
38
+ end
39
+
40
+ def do_transactions2_smb1(request)
41
+ raise NotImplementedError
42
+ end
43
+
28
44
  def do_close_smb2(request)
29
45
  raise NotImplementedError
30
46
  end
@@ -6,17 +6,31 @@ module RubySMB
6
6
  # @return [String]
7
7
  attr_accessor :session_key
8
8
 
9
- # Take an SMB1 packet and sign it.
9
+ # Take an SMB1 packet and sign it. This version is an instance method that
10
+ # accesses the necessary values from the object instance.
10
11
  #
11
12
  # @param [RubySMB::GenericPacket] packet The packet to sign.
12
13
  # @return [RubySMB::GenericPacket] the signed packet
13
14
  def smb1_sign(packet)
15
+ packet = Signing::smb1_sign(packet, @session_key, @sequence_counter)
16
+ @sequence_counter += 1
17
+
18
+ packet
19
+ end
20
+
21
+ # Take an SMB1 packet and sign it. This version is a module function that
22
+ # requires the necessary values to be explicitly passed to it.
23
+ #
24
+ # @param [RubySMB::GenericPacket] packet The packet to sign.
25
+ # @param [String] session_key The key to use for signing.
26
+ # @param [Integer] sequence_counter The sequence counter of packet to be sent.
27
+ # @return [RubySMB::GenericPacket] the signed packet
28
+ def self.smb1_sign(packet, session_key, sequence_counter)
14
29
  # Pack the Sequence counter into a int64le
15
- packed_sequence_counter = [@sequence_counter].pack('Q<')
30
+ packed_sequence_counter = [sequence_counter].pack('Q<')
16
31
  packet.smb_header.security_features = packed_sequence_counter
17
- signature = OpenSSL::Digest::MD5.digest(@session_key + packet.to_binary_s)[0, 8]
32
+ signature = OpenSSL::Digest::MD5.digest(session_key + packet.to_binary_s)[0, 8]
18
33
  packet.smb_header.security_features = signature
19
- @sequence_counter += 1
20
34
 
21
35
  packet
22
36
  end
@@ -30,7 +30,7 @@ module RubySMB
30
30
  bit1 :generic_execute, label: 'Generic Execute'
31
31
  bit1 :generic_all, label: 'Generic All'
32
32
  bit2 :reserved3
33
- bit1 :maximum, label: 'Maximum Allowed'
33
+ bit1 :maximum_allowed, label: 'Maximum Allowed'
34
34
  bit1 :system_security, label: 'System Security'
35
35
  end
36
36
  end
@@ -30,7 +30,7 @@ module RubySMB
30
30
  bit1 :generic_execute, label: 'Generic Execute'
31
31
  bit1 :generic_all, label: 'Generic All'
32
32
  bit2 :reserved3
33
- bit1 :maximum, label: 'Maximum Allowed'
33
+ bit1 :maximum_allowed, label: 'Maximum Allowed'
34
34
  bit1 :system_security, label: 'System Security'
35
35
  end
36
36
  end