ruby_smb 3.1.1 → 3.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +1 -2
- data/examples/file_server.rb +8 -1
- data/examples/virtual_file_server.rb +143 -0
- data/lib/ruby_smb/fscc/file_information/file_access_information.rb +15 -0
- data/lib/ruby_smb/fscc/file_information/file_alignment_information.rb +45 -0
- data/lib/ruby_smb/fscc/file_information/file_all_information.rb +23 -0
- data/lib/ruby_smb/fscc/file_information/file_basic_information.rb +20 -0
- data/lib/ruby_smb/fscc/file_information/file_both_directory_information.rb +3 -3
- data/lib/ruby_smb/fscc/file_information/file_directory_information.rb +3 -3
- data/lib/ruby_smb/fscc/file_information/file_ea_information.rb +1 -0
- data/lib/ruby_smb/fscc/file_information/file_full_directory_information.rb +3 -3
- data/lib/ruby_smb/fscc/file_information/file_id_both_directory_information.rb +3 -3
- data/lib/ruby_smb/fscc/file_information/file_id_full_directory_information.rb +3 -3
- data/lib/ruby_smb/fscc/file_information/file_internal_information.rb +15 -0
- data/lib/ruby_smb/fscc/file_information/file_mode_information.rb +29 -0
- data/lib/ruby_smb/fscc/file_information/file_name_information.rb +16 -0
- data/lib/ruby_smb/fscc/file_information/file_names_information.rb +1 -1
- data/lib/ruby_smb/fscc/file_information/file_normalized_name_information.rb +16 -0
- data/lib/ruby_smb/fscc/file_information/file_position_information.rb +15 -0
- data/lib/ruby_smb/fscc/file_information/file_rename_information.rb +1 -1
- data/lib/ruby_smb/fscc/file_information/file_standard_information.rb +20 -0
- data/lib/ruby_smb/fscc/file_information/file_stream_information.rb +3 -0
- data/lib/ruby_smb/fscc/file_information.rb +41 -8
- data/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information.rb +1 -0
- data/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information.rb +1 -0
- data/lib/ruby_smb/fscc/file_system_information.rb +4 -0
- data/lib/ruby_smb/gss/provider/ntlm.rb +16 -3
- data/lib/ruby_smb/gss/provider.rb +10 -1
- data/lib/ruby_smb/server/server_client/session_setup.rb +4 -2
- data/lib/ruby_smb/server/server_client.rb +9 -1
- data/lib/ruby_smb/server/session.rb +5 -1
- data/lib/ruby_smb/server/share/provider/disk/processor/close.rb +9 -5
- data/lib/ruby_smb/server/share/provider/disk/processor/create.rb +2 -2
- data/lib/ruby_smb/server/share/provider/disk/processor/query.rb +2 -2
- data/lib/ruby_smb/server/share/provider/disk/processor/read.rb +15 -14
- data/lib/ruby_smb/server/share/provider/disk/processor.rb +76 -12
- data/lib/ruby_smb/server/share/provider/disk.rb +8 -2
- data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_file.rb +85 -0
- data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname.rb +196 -0
- data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat.rb +175 -0
- data/lib/ruby_smb/server/share/provider/virtual_disk.rb +116 -0
- data/lib/ruby_smb/server/share/provider.rb +1 -0
- data/lib/ruby_smb/server.rb +13 -3
- data/lib/ruby_smb/smb2/tree.rb +1 -0
- data/lib/ruby_smb/version.rb +1 -1
- data/spec/lib/ruby_smb/fscc/file_information/file_access_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_alignment_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_all_information_spec.rb +61 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_basic_information_spec.rb +41 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_both_directory_information_spec.rb +59 -10
- data/spec/lib/ruby_smb/fscc/file_information/file_directory_information_spec.rb +30 -12
- data/spec/lib/ruby_smb/fscc/file_information/file_ea_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_full_directory_information_spec.rb +30 -12
- data/spec/lib/ruby_smb/fscc/file_information/file_id_both_directory_information_spec.rb +63 -10
- data/spec/lib/ruby_smb/fscc/file_information/file_id_full_directory_information_spec.rb +30 -12
- data/spec/lib/ruby_smb/fscc/file_information/file_internal_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_mode_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_name_information_spec.rb +44 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_names_information_spec.rb +30 -12
- data/spec/lib/ruby_smb/fscc/file_information/file_network_open_information_spec.rb +51 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_normalized_name_information_spec.rb +44 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_position_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_rename_information_spec.rb +1 -1
- data/spec/lib/ruby_smb/fscc/file_information/file_standard_information_spec.rb +41 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_stream_information_spec.rb +51 -0
- data/spec/lib/ruby_smb/fscc/file_information_spec.rb +14 -0
- data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information_spec.rb +46 -0
- data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information_spec.rb +51 -0
- data/spec/lib/ruby_smb/fscc/file_system_information_spec.rb +14 -0
- data/spec/lib/ruby_smb/server/server_client_spec.rb +15 -0
- data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname_spec.rb +581 -0
- data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat_spec.rb +207 -0
- data/spec/lib/ruby_smb/server/share/provider/virtual_disk_spec.rb +122 -0
- data.tar.gz.sig +0 -0
- metadata +57 -2
- metadata.gz.sig +0 -0
@@ -17,10 +17,26 @@ module RubySMB
|
|
17
17
|
# contents of a directory.
|
18
18
|
FILE_BOTH_DIRECTORY_INFORMATION = 0x03
|
19
19
|
|
20
|
+
# Information class used to query or set file information.
|
21
|
+
FILE_BASIC_INFORMATION = 0x04
|
22
|
+
|
23
|
+
# Information class is used to query file information.
|
24
|
+
FILE_STANDARD_INFORMATION = 0x05
|
25
|
+
|
26
|
+
# Information class used to query for the file system's 64-bit file ID.
|
27
|
+
FILE_INTERNAL_INFORMATION = 0x06
|
28
|
+
|
20
29
|
# Information class used to query for the size of the extended attributes
|
21
30
|
# (EA) for a file.
|
22
31
|
FILE_EA_INFORMATION = 0x07
|
23
32
|
|
33
|
+
# Information class used to query the access rights of a file that were
|
34
|
+
# granted when the file was opened.
|
35
|
+
FILE_ACCESS_INFORMATION = 0x08
|
36
|
+
|
37
|
+
# Information class is used locally to query the name of a file.
|
38
|
+
FILE_NAME_INFORMATION = 0x09
|
39
|
+
|
24
40
|
# Information class used to rename a file.
|
25
41
|
FILE_RENAME_INFORMATION = 0x0A
|
26
42
|
|
@@ -31,6 +47,17 @@ module RubySMB
|
|
31
47
|
# Information class used to mark a file for deletion.
|
32
48
|
FILE_DISPOSITION_INFORMATION = 0x0D
|
33
49
|
|
50
|
+
# Information class used to query or set the position of the file pointer
|
51
|
+
# within a file.
|
52
|
+
FILE_POSITION_INFORMATION = 0x0E
|
53
|
+
|
54
|
+
# Information class used to query or set the mode of the file.
|
55
|
+
FILE_MODE_INFORMATION = 0x10
|
56
|
+
|
57
|
+
# Information class used to query the buffer alignment required by the
|
58
|
+
# underlying device.
|
59
|
+
FILE_ALIGNMENT_INFORMATION = 0x11
|
60
|
+
|
34
61
|
# Information class used to enumerate the data streams of a file or a
|
35
62
|
# directory.
|
36
63
|
FILE_STREAM_INFORMATION = 0x16
|
@@ -57,6 +84,10 @@ module RubySMB
|
|
57
84
|
FILE_NORMALIZED_NAME_INFORMATION = 0x30
|
58
85
|
|
59
86
|
|
87
|
+
# Information class is used to query a collection of file information
|
88
|
+
# structures.
|
89
|
+
FILE_ALL_INFORMATION = 0x12
|
90
|
+
|
60
91
|
# These Information Classes can be used by SMB1 using the pass-through
|
61
92
|
# Information Levels when available on the server (CAP_INFOLEVEL_PASSTHRU
|
62
93
|
# capability flag in an SMB_COM_NEGOTIATE server response). The constant
|
@@ -69,25 +100,27 @@ module RubySMB
|
|
69
100
|
constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value }
|
70
101
|
end
|
71
102
|
|
72
|
-
# The FILE_NAME_INFORMATION type as defined in
|
73
|
-
# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/20406fb1-605f-4629-ba9a-c67ee25f23d2
|
74
|
-
class FileNameInformation < BinData::Record
|
75
|
-
endian :little
|
76
|
-
uint32 :file_name_length, label: 'File Name Length', initial_value: -> { file_name.do_num_bytes }
|
77
|
-
string16 :file_name, label: 'File Name', read_length: -> { file_name_length }
|
78
|
-
end
|
79
|
-
|
80
103
|
require 'ruby_smb/fscc/file_information/file_directory_information'
|
81
104
|
require 'ruby_smb/fscc/file_information/file_full_directory_information'
|
82
105
|
require 'ruby_smb/fscc/file_information/file_disposition_information'
|
83
106
|
require 'ruby_smb/fscc/file_information/file_id_full_directory_information'
|
84
107
|
require 'ruby_smb/fscc/file_information/file_both_directory_information'
|
85
108
|
require 'ruby_smb/fscc/file_information/file_id_both_directory_information'
|
109
|
+
require 'ruby_smb/fscc/file_information/file_name_information'
|
86
110
|
require 'ruby_smb/fscc/file_information/file_names_information'
|
111
|
+
require 'ruby_smb/fscc/file_information/file_normalized_name_information'
|
87
112
|
require 'ruby_smb/fscc/file_information/file_rename_information'
|
88
113
|
require 'ruby_smb/fscc/file_information/file_network_open_information'
|
89
114
|
require 'ruby_smb/fscc/file_information/file_ea_information'
|
90
115
|
require 'ruby_smb/fscc/file_information/file_stream_information'
|
116
|
+
require 'ruby_smb/fscc/file_information/file_basic_information'
|
117
|
+
require 'ruby_smb/fscc/file_information/file_standard_information'
|
118
|
+
require 'ruby_smb/fscc/file_information/file_internal_information'
|
119
|
+
require 'ruby_smb/fscc/file_information/file_access_information'
|
120
|
+
require 'ruby_smb/fscc/file_information/file_position_information'
|
121
|
+
require 'ruby_smb/fscc/file_information/file_mode_information'
|
122
|
+
require 'ruby_smb/fscc/file_information/file_alignment_information'
|
123
|
+
require 'ruby_smb/fscc/file_information/file_all_information'
|
91
124
|
end
|
92
125
|
end
|
93
126
|
end
|
@@ -7,6 +7,7 @@ module RubySMB
|
|
7
7
|
CLASS_LEVEL = FileSystemInformation::FILE_FS_ATTRIBUTE_INFORMATION
|
8
8
|
|
9
9
|
endian :little
|
10
|
+
|
10
11
|
struct :file_system_attributes, label: 'File System Attributes' do
|
11
12
|
bit1 :file_supports_reparse_points, label: 'FS Supports Reparse Points'
|
12
13
|
bit1 :file_supports_sparse_files, label: 'FS Supports Sparse Files'
|
@@ -7,6 +7,7 @@ module RubySMB
|
|
7
7
|
CLASS_LEVEL = FileSystemInformation::FILE_FS_VOLUME_INFORMATION
|
8
8
|
|
9
9
|
endian :little
|
10
|
+
|
10
11
|
file_time :volume_creation_time, label: 'Volume Creation Time'
|
11
12
|
uint32 :volume_serial_number, label: 'Volume Serial Number'
|
12
13
|
uint32 :volume_label_length, label: 'Volume Label Length', initial_value: -> { volume_label.do_num_bytes }
|
@@ -15,6 +15,10 @@ module RubySMB
|
|
15
15
|
FILE_FS_VOLUME_FLAGS_INFORMATION = 10
|
16
16
|
FILE_FS_SECTOR_SIZE_INFORMATION = 11
|
17
17
|
|
18
|
+
def self.name(value)
|
19
|
+
constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value }
|
20
|
+
end
|
21
|
+
|
18
22
|
require 'ruby_smb/fscc/file_system_information/file_fs_attribute_information'
|
19
23
|
require 'ruby_smb/fscc/file_system_information/file_fs_volume_information'
|
20
24
|
end
|
@@ -113,6 +113,7 @@ module RubySMB
|
|
113
113
|
def process_ntlm_type3(type3_msg)
|
114
114
|
if type3_msg.user == '' && type3_msg.domain == ''
|
115
115
|
if @provider.allow_anonymous
|
116
|
+
@session_key = "\x00".b * 16 # see MS-NLMP section 3.4
|
116
117
|
return WindowsError::NTStatus::STATUS_SUCCESS
|
117
118
|
end
|
118
119
|
|
@@ -126,6 +127,12 @@ module RubySMB
|
|
126
127
|
domain: type3_msg.domain
|
127
128
|
)
|
128
129
|
if account.nil?
|
130
|
+
if @provider.allow_guests
|
131
|
+
logger.info("NTLM authentication request succeeded for #{dbg_string} (guest)")
|
132
|
+
@session_key = "\x00".b * 16 # see MS-NLMP section 3.4
|
133
|
+
return WindowsError::NTStatus::STATUS_SUCCESS
|
134
|
+
end
|
135
|
+
|
129
136
|
logger.info("NTLM authentication request failed for #{dbg_string} (no account)")
|
130
137
|
return WindowsError::NTStatus::STATUS_LOGON_FAILURE
|
131
138
|
end
|
@@ -233,25 +240,31 @@ module RubySMB
|
|
233
240
|
domain: type3_msg.domain
|
234
241
|
)
|
235
242
|
if account.nil?
|
236
|
-
if
|
243
|
+
if type3_msg.user == ''
|
244
|
+
is_guest = false
|
237
245
|
identity = IDENTITY_ANONYMOUS
|
246
|
+
else
|
247
|
+
is_guest = true
|
248
|
+
identity = Account.new(type3_msg.user.encode(''.encoding), '', type3_msg.domain.encode(''.encoding)).to_s
|
238
249
|
end
|
239
250
|
else
|
251
|
+
is_guest = false
|
240
252
|
identity = account.to_s
|
241
253
|
end
|
242
254
|
end
|
243
255
|
|
244
|
-
Result.new(buffer, nt_status, identity)
|
256
|
+
Result.new(buffer, nt_status, identity, is_guest)
|
245
257
|
end
|
246
258
|
end
|
247
259
|
|
248
260
|
# @param [Boolean] allow_anonymous whether or not to allow anonymous authentication attempts
|
249
261
|
# @param [String] default_domain the default domain to use for authentication, unless specified 'WORKGROUP' will
|
250
262
|
# be used
|
251
|
-
def initialize(allow_anonymous: false, default_domain: 'WORKGROUP')
|
263
|
+
def initialize(allow_anonymous: false, allow_guests: false, default_domain: 'WORKGROUP')
|
252
264
|
raise ArgumentError, 'Must specify a default domain' unless default_domain
|
253
265
|
|
254
266
|
@allow_anonymous = allow_anonymous
|
267
|
+
@allow_guests = allow_guests
|
255
268
|
@default_domain = default_domain
|
256
269
|
@accounts = []
|
257
270
|
@generate_server_challenge = -> { SecureRandom.bytes(8) }
|
@@ -7,7 +7,11 @@ module RubySMB
|
|
7
7
|
# A special constant implying that the authenticated user is anonymous.
|
8
8
|
IDENTITY_ANONYMOUS = :anonymous
|
9
9
|
# The result of a processed GSS request.
|
10
|
-
Result = Struct.new(:buffer, :nt_status, :identity)
|
10
|
+
Result = Struct.new(:buffer, :nt_status, :identity, :is_guest) do
|
11
|
+
def is_anonymous
|
12
|
+
identity == Gss::Provider::IDENTITY_ANONYMOUS
|
13
|
+
end
|
14
|
+
end
|
11
15
|
|
12
16
|
#
|
13
17
|
# The base class for a GSS authentication provider. This class defines a common interface and is not usable as a
|
@@ -26,6 +30,11 @@ module RubySMB
|
|
26
30
|
# Whether or not anonymous authentication attempts should be permitted.
|
27
31
|
#
|
28
32
|
attr_accessor :allow_anonymous
|
33
|
+
|
34
|
+
#
|
35
|
+
# Whether or not unknown users should be allowed to authenticate as guests.
|
36
|
+
#
|
37
|
+
attr_accessor :allow_guests
|
29
38
|
end
|
30
39
|
end
|
31
40
|
end
|
@@ -80,12 +80,14 @@ module RubySMB
|
|
80
80
|
if gss_result.nt_status == WindowsError::NTStatus::STATUS_SUCCESS
|
81
81
|
session.state = :valid
|
82
82
|
session.user_id = gss_result.identity
|
83
|
+
session.is_guest = !!gss_result.is_guest
|
83
84
|
session.key = @gss_authenticator.session_key
|
84
|
-
session.signing_required = request.security_mode.signing_required == 1
|
85
|
+
session.signing_required = request.security_mode.signing_required == 1 || (!session.is_guest && !session.is_anonymous)
|
85
86
|
|
86
87
|
response.smb2_header.credits = 32
|
87
|
-
@cipher_id = 0 if session.is_anonymous # disable encryption for anonymous users
|
88
|
+
@cipher_id = 0 if session.is_anonymous || session.is_guest # disable encryption for anonymous users and guest users which have a null session key
|
88
89
|
response.session_flags.encrypt_data = 1 unless @cipher_id == 0
|
90
|
+
response.session_flags.guest = session.is_guest
|
89
91
|
elsif gss_result.nt_status == WindowsError::NTStatus::STATUS_MORE_PROCESSING_REQUIRED && @dialect == '0x0311'
|
90
92
|
update_preauth_hash(response)
|
91
93
|
end
|
@@ -19,7 +19,7 @@ module RubySMB
|
|
19
19
|
include RubySMB::Server::ServerClient::ShareIO
|
20
20
|
include RubySMB::Server::ServerClient::TreeConnect
|
21
21
|
|
22
|
-
attr_reader :dialect, :session_table
|
22
|
+
attr_reader :dialect, :dispatcher, :session_table
|
23
23
|
|
24
24
|
# @param [Server] server the server that accepted this connection
|
25
25
|
# @param [Dispatcher::Socket] dispatcher the connection's socket dispatcher
|
@@ -57,6 +57,14 @@ module RubySMB
|
|
57
57
|
@dispatcher.tcp_socket.getpeername
|
58
58
|
end
|
59
59
|
|
60
|
+
def peerhost
|
61
|
+
::Socket::unpack_sockaddr_in(getpeername)[1]
|
62
|
+
end
|
63
|
+
|
64
|
+
def peerport
|
65
|
+
::Socket::unpack_sockaddr_in(getpeername)[0]
|
66
|
+
end
|
67
|
+
|
60
68
|
#
|
61
69
|
# Handle a request after the dialect has been negotiated. This is the main
|
62
70
|
# handler for all requests after the connection has been established. If a
|
@@ -64,7 +64,7 @@ module RubySMB
|
|
64
64
|
attr_accessor :tree_connect_table
|
65
65
|
|
66
66
|
# Untyped hash for storing additional arbitrary metadata about the current session
|
67
|
-
# @!attribute [rw]
|
67
|
+
# @!attribute [rw] metadata
|
68
68
|
# @return [Hash]
|
69
69
|
attr_accessor :metadata
|
70
70
|
|
@@ -72,6 +72,10 @@ module RubySMB
|
|
72
72
|
# @!attribute [r] creation_time
|
73
73
|
# @return [Time]
|
74
74
|
attr_reader :creation_time
|
75
|
+
|
76
|
+
# Whether or not the authenticated user is a guest.
|
77
|
+
# @!attribute [rw] is_guest
|
78
|
+
attr_accessor :is_guest
|
75
79
|
end
|
76
80
|
end
|
77
81
|
end
|
@@ -9,27 +9,31 @@ module RubySMB
|
|
9
9
|
module Close
|
10
10
|
def do_close_smb1(request)
|
11
11
|
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/99b767e2-8f0e-438b-ace5-4323940f2dc8
|
12
|
-
|
12
|
+
handle = @handles.delete(request.parameter_block.fid)
|
13
|
+
if handle.nil?
|
13
14
|
response = RubySMB::SMB1::Packet::EmptyPacket.new
|
14
15
|
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE
|
15
16
|
return response
|
16
17
|
end
|
17
18
|
|
19
|
+
handle.file.close if handle.file
|
20
|
+
|
18
21
|
response = RubySMB::SMB1::Packet::CloseResponse.new
|
19
22
|
response
|
20
23
|
end
|
21
24
|
|
22
25
|
def do_close_smb2(request)
|
23
|
-
|
24
|
-
if
|
26
|
+
handle = @handles.delete(request.file_id.to_binary_s)
|
27
|
+
if handle.nil?
|
25
28
|
response = RubySMB::SMB2::Packet::ErrorPacket.new
|
26
29
|
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
|
27
30
|
return response
|
28
31
|
end
|
29
32
|
|
30
|
-
|
33
|
+
handle.file.close if handle.file
|
34
|
+
|
31
35
|
response = RubySMB::SMB2::Packet::CloseResponse.new
|
32
|
-
set_common_info(response, local_path)
|
36
|
+
set_common_info(response, handle.local_path)
|
33
37
|
response.flags = 1
|
34
38
|
response
|
35
39
|
end
|
@@ -38,7 +38,7 @@ module RubySMB
|
|
38
38
|
block.allocation_size = get_allocation_size(local_path)
|
39
39
|
end
|
40
40
|
|
41
|
-
@handles[response.parameter_block.fid] = Handle.new(path, local_path, false)
|
41
|
+
@handles[response.parameter_block.fid] = Handle.new(path, local_path, false, local_path.file? ? local_path.open : nil)
|
42
42
|
response
|
43
43
|
end
|
44
44
|
|
@@ -131,7 +131,7 @@ module RubySMB
|
|
131
131
|
response.contexts_length = 0
|
132
132
|
end
|
133
133
|
|
134
|
-
@handles[response.file_id.to_binary_s] = Handle.new(path, local_path, durable)
|
134
|
+
@handles[response.file_id.to_binary_s] = Handle.new(path, local_path, durable, local_path.file? ? local_path.open : nil)
|
135
135
|
response
|
136
136
|
end
|
137
137
|
end
|
@@ -315,7 +315,7 @@ module RubySMB
|
|
315
315
|
|
316
316
|
case request.file_information_class
|
317
317
|
when Fscc::FileInformation::FILE_NORMALIZED_NAME_INFORMATION
|
318
|
-
info = Fscc::FileInformation::
|
318
|
+
info = Fscc::FileInformation::FileNormalizedNameInformation.new(file_name: @handles[request.file_id.to_binary_s].remote_path)
|
319
319
|
else
|
320
320
|
info = build_fscc_file_information(local_path, request.file_information_class)
|
321
321
|
end
|
@@ -344,7 +344,7 @@ module RubySMB
|
|
344
344
|
volume_label: provider.name
|
345
345
|
)
|
346
346
|
else
|
347
|
-
logger.warn("Can not handle QUERY_INFO request for type: #{request.info_type}, class: #{request.file_information_class}")
|
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
348
|
raise NotImplementedError
|
349
349
|
end
|
350
350
|
|
@@ -9,18 +9,16 @@ module RubySMB
|
|
9
9
|
module Read
|
10
10
|
def do_read_andx_smb1(request)
|
11
11
|
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/bb8fcb6a-3032-46a1-ad4a-c0d7892921f9
|
12
|
-
|
12
|
+
handle = @handles[request.parameter_block.fid]
|
13
13
|
|
14
|
-
if
|
14
|
+
if handle.nil? || handle.file.nil?
|
15
15
|
response = SMB1::Packet::EmptyPacket.new
|
16
16
|
response.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE
|
17
|
+
return response
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
file.seek(request.parameter_block.offset.snapshot)
|
22
|
-
buffer = file.read(request.parameter_block.max_count_of_bytes_to_return.snapshot)
|
23
|
-
end
|
20
|
+
handle.file.seek(request.parameter_block.offset.snapshot)
|
21
|
+
buffer = handle.file.read(request.parameter_block.max_count_of_bytes_to_return.snapshot)
|
24
22
|
|
25
23
|
response = SMB1::Packet::ReadAndxResponse.new
|
26
24
|
response.parameter_block.available = 0xffff # this field is only used for named pipes, must be -1 for all others
|
@@ -33,20 +31,23 @@ module RubySMB
|
|
33
31
|
end
|
34
32
|
|
35
33
|
def do_read_smb2(request)
|
36
|
-
|
37
|
-
if
|
34
|
+
handle = @handles[request.file_id.to_binary_s]
|
35
|
+
if handle.nil?
|
38
36
|
response = RubySMB::SMB2::Packet::ErrorPacket.new
|
39
37
|
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_FILE_CLOSED
|
40
38
|
return response
|
41
39
|
end
|
42
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
|
+
|
43
47
|
raise NotImplementedError unless request.channel == SMB2::SMB2_CHANNEL_NONE
|
44
48
|
|
45
|
-
|
46
|
-
|
47
|
-
file.seek(request.offset.snapshot)
|
48
|
-
buffer = file.read(request.read_length)
|
49
|
-
end
|
49
|
+
handle.file.seek(request.offset.snapshot)
|
50
|
+
buffer = handle.file.read(request.read_length)
|
50
51
|
|
51
52
|
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/21e8b343-34e9-4fca-8d93-03dd2d3e961e
|
52
53
|
if buffer.nil? || buffer.length == 0 || buffer.length < request.min_bytes
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'zlib'
|
1
2
|
require 'ruby_smb/server/share/provider/processor'
|
2
3
|
|
3
4
|
module RubySMB
|
@@ -16,7 +17,7 @@ module RubySMB
|
|
16
17
|
include RubySMB::Server::Share::Provider::Disk::Processor::Query
|
17
18
|
include RubySMB::Server::Share::Provider::Disk::Processor::Read
|
18
19
|
|
19
|
-
Handle = Struct.new(:remote_path, :local_path, :durable
|
20
|
+
Handle = Struct.new(:remote_path, :local_path, :durable?, :file)
|
20
21
|
def initialize(provider, server_client, session)
|
21
22
|
super
|
22
23
|
@handles = {}
|
@@ -30,6 +31,25 @@ module RubySMB
|
|
30
31
|
)
|
31
32
|
end
|
32
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
|
+
|
33
53
|
private
|
34
54
|
|
35
55
|
def build_fscc_file_attributes(path)
|
@@ -44,6 +64,29 @@ module RubySMB
|
|
44
64
|
|
45
65
|
def build_fscc_file_information(path, info_class, rename: nil)
|
46
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)
|
47
90
|
when Fscc::FileInformation::FILE_EA_INFORMATION
|
48
91
|
info = Fscc::FileInformation::FileEaInformation.new
|
49
92
|
when Fscc::FileInformation::FILE_FULL_DIRECTORY_INFORMATION
|
@@ -54,21 +97,42 @@ module RubySMB
|
|
54
97
|
info = Fscc::FileInformation::FileIdBothDirectoryInformation.new
|
55
98
|
set_common_info(info, path)
|
56
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
|
57
112
|
when Fscc::FileInformation::FILE_NETWORK_OPEN_INFORMATION
|
58
113
|
info = Fscc::FileInformation::FileNetworkOpenInformation.new
|
59
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
|
60
123
|
when Fscc::FileInformation::FILE_STREAM_INFORMATION
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
+
)
|
70
133
|
else
|
71
|
-
|
134
|
+
logger.warn("Unsupported FSCC file information class: #{info_class} (#{Fscc::FileInformation.name(info_class)})")
|
135
|
+
raise NotImplementedError
|
72
136
|
end
|
73
137
|
|
74
138
|
# some have a next offset field that needs to be set accordingly
|
@@ -96,7 +160,7 @@ module RubySMB
|
|
96
160
|
path = path.encode.gsub(/\/|\\/, File::SEPARATOR)
|
97
161
|
path = path.delete_prefix(File::SEPARATOR)
|
98
162
|
local_path = (provider.path + path.encode).cleanpath
|
99
|
-
unless local_path == provider.path || local_path.to_s.start_with?(provider.path.to_s +
|
163
|
+
unless local_path == provider.path || local_path.to_s.start_with?(provider.path.to_s.delete_suffix(File::SEPARATOR) + File::SEPARATOR)
|
100
164
|
raise RuntimeError, "Directory traversal detected to: #{local_path}"
|
101
165
|
end
|
102
166
|
else
|
@@ -5,14 +5,20 @@ module RubySMB
|
|
5
5
|
class Server
|
6
6
|
module Share
|
7
7
|
module Provider
|
8
|
+
# This is a share provider that exposes the local file system.
|
8
9
|
class Disk < Base
|
9
10
|
TYPE = TYPE_DISK
|
10
11
|
# emulate NTFS just like Samba does
|
11
12
|
FILE_SYSTEM = FileSystem::NTFS
|
12
13
|
|
14
|
+
# @param [String] name The name of this share.
|
15
|
+
# @param [String, Pathname] path The local file system path to share. This path must be an absolute path to an existing
|
16
|
+
# directory.
|
13
17
|
def initialize(name, path)
|
14
|
-
path = Pathname.new(File.expand_path(path))
|
15
|
-
raise ArgumentError unless path.directory?
|
18
|
+
path = Pathname.new(File.expand_path(path)) if path.is_a?(String)
|
19
|
+
raise ArgumentError.new('path must be a directory') unless path.directory? # it needs to exist
|
20
|
+
raise ArgumentError.new('path must be absolute') unless path.absolute? # it needs to be absolute so it is independent of the cwd
|
21
|
+
|
16
22
|
@path = path
|
17
23
|
super(name)
|
18
24
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'ruby_smb/server/share/provider/virtual_disk/virtual_pathname'
|
2
|
+
require 'ruby_smb/server/share/provider/virtual_disk/virtual_stat'
|
3
|
+
|
4
|
+
module RubySMB
|
5
|
+
class Server
|
6
|
+
module Share
|
7
|
+
module Provider
|
8
|
+
class VirtualDisk < Disk
|
9
|
+
# A dynamic file is one whose contents are generated by the specified
|
10
|
+
# block.
|
11
|
+
class VirtualDynamicFile < VirtualPathname
|
12
|
+
# @param [Hash] disk The mapping of paths to objects representing the virtual file system.
|
13
|
+
# @param [String] path The path of this entry.
|
14
|
+
# @param [File::Stat] stat An explicit stat object describing the file.
|
15
|
+
def initialize(disk, path, stat: nil, &block)
|
16
|
+
raise ArgumentError.new('a generation block must be specified') if block.nil?
|
17
|
+
|
18
|
+
@content_generator = block
|
19
|
+
super(disk, path)
|
20
|
+
@stat = stat
|
21
|
+
end
|
22
|
+
|
23
|
+
def generate(server_client, session)
|
24
|
+
content = @content_generator.call(server_client, session)
|
25
|
+
VirtualStaticFile.new(@virtual_disk, @path, content, stat: @stat)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# A static file is one whose contents are known at creation time and
|
30
|
+
# do not change.
|
31
|
+
class VirtualStaticFile < VirtualPathname
|
32
|
+
# @param [Hash] disk The mapping of paths to objects representing the virtual file system.
|
33
|
+
# @param [String] path The path of this entry.
|
34
|
+
# @param [String] content The static content of this file.
|
35
|
+
# @param [File::Stat] stat An explicit stat object describing the file.
|
36
|
+
def initialize(disk, path, content, stat: nil)
|
37
|
+
stat = stat || VirtualStat.new(file?: true, size: content.size)
|
38
|
+
raise ArgumentError.new('stat is not a file') unless stat.file?
|
39
|
+
|
40
|
+
@content = content
|
41
|
+
super(disk, path, stat: stat)
|
42
|
+
end
|
43
|
+
|
44
|
+
def open(mode = 'r', &block)
|
45
|
+
file = StringIO.new(@content)
|
46
|
+
block_given? ? block.call(file) : file
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_reader :content
|
50
|
+
end
|
51
|
+
|
52
|
+
# A mapped file is one who is backed by an entry on disk. The path
|
53
|
+
# need not be present, but if it does exist, it must be a file.
|
54
|
+
class VirtualMappedFile < VirtualPathname
|
55
|
+
# @param [Hash] disk The mapping of paths to objects representing the virtual file system.
|
56
|
+
# @param [String] path The path of this entry.
|
57
|
+
# @param [String, Pathname] mapped_path The path on the local file system to map into the virtual file system.
|
58
|
+
def initialize(disk, path, mapped_path)
|
59
|
+
mapped_path = Pathname.new(File.expand_path(mapped_path)) if mapped_path.is_a?(String)
|
60
|
+
raise ArgumentError.new('mapped_path must be absolute') unless mapped_path.absolute? # it needs to be absolute so it is independent of the cwd
|
61
|
+
|
62
|
+
@virtual_disk = disk
|
63
|
+
@path = path
|
64
|
+
@mapped_path = mapped_path
|
65
|
+
end
|
66
|
+
|
67
|
+
def exist?
|
68
|
+
# filter out anything that's not a directory but allow the file to be missing, this prevents exposing
|
69
|
+
# directories which could yield path confusion errors
|
70
|
+
@mapped_path.exist? && @mapped_path.file?
|
71
|
+
end
|
72
|
+
|
73
|
+
def stat
|
74
|
+
@mapped_path.stat
|
75
|
+
end
|
76
|
+
|
77
|
+
def open(mode = 'r', &block)
|
78
|
+
@mapped_path.open(mode, &block)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|