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