ruby_smb 3.1.1 → 3.1.4
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 +6 -61
- data/examples/virtual_file_server.rb +91 -0
- data/lib/ruby_smb/client/authentication.rb +12 -2
- data/lib/ruby_smb/client/negotiation.rb +2 -0
- data/lib/ruby_smb/client.rb +8 -2
- data/lib/ruby_smb/error.rb +4 -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/gss.rb +1 -0
- data/lib/ruby_smb/ntlm/client.rb +74 -0
- data/lib/ruby_smb/ntlm.rb +1 -0
- data/lib/ruby_smb/server/cli.rb +121 -0
- 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 +14 -3
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +11 -0
- 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/ntlm/client/session_spec.rb +114 -0
- data/spec/lib/ruby_smb/ntlm/client_spec.rb +36 -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 +63 -2
- metadata.gz.sig +0 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
module RubySMB
|
2
|
+
module Fscc
|
3
|
+
module FileInformation
|
4
|
+
# The FileStandardInformation Class as defined in
|
5
|
+
# [2.4.41 FileStandardInformation](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/5afa7f66-619c-48f3-955f-68c4ece704ae)
|
6
|
+
class FileStandardInformation < BinData::Record
|
7
|
+
CLASS_LEVEL = FileInformation::FILE_STANDARD_INFORMATION
|
8
|
+
|
9
|
+
endian :little
|
10
|
+
|
11
|
+
int64 :allocation_size, label: 'Allocation Size'
|
12
|
+
int64 :end_of_file, label: 'End of File'
|
13
|
+
uint32 :number_of_links, label: 'Number of Links'
|
14
|
+
int8 :delete_pending, label: 'Delete Pending'
|
15
|
+
int8 :directory, label: 'Directory'
|
16
|
+
string :reserved, label: 'Reserved', length: 2
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -4,7 +4,10 @@ module RubySMB
|
|
4
4
|
# The FileStreamInformation
|
5
5
|
# [2.4.43 FileStreamInformation](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/f8762be6-3ab9-411e-a7d6-5cc68f70c78d)
|
6
6
|
class FileStreamInformation < BinData::Record
|
7
|
+
CLASS_LEVEL = Fscc::FileInformation::FILE_STREAM_INFORMATION
|
8
|
+
|
7
9
|
endian :little
|
10
|
+
|
8
11
|
uint32 :next_entry_offset, label: 'Next Entry Offset'
|
9
12
|
uint32 :stream_name_length, label: 'Stream Name Length', initial_value: -> { stream_name.do_num_bytes }
|
10
13
|
int64 :stream_size, label: 'Stream Size'
|
@@ -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
|
data/lib/ruby_smb/gss.rb
CHANGED
@@ -14,6 +14,7 @@ module RubySMB
|
|
14
14
|
# @param asn The ASN object to apply the traversal path on.
|
15
15
|
# @param [Array] path The path to traverse, each element is passed to the
|
16
16
|
# ASN object's #value's #[] operator.
|
17
|
+
# @return [OpenSSL::ASN1::Sequence, nil]
|
17
18
|
def self.asn1dig(asn, *path)
|
18
19
|
path.each do |part|
|
19
20
|
return nil unless asn&.value
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module RubySMB::NTLM
|
2
|
+
module Message
|
3
|
+
def deflag
|
4
|
+
security_buffers.inject(head_size) do |cur, a|
|
5
|
+
a[1].offset = cur
|
6
|
+
cur += a[1].data_size
|
7
|
+
has_flag?(:UNICODE) ? cur + cur % 2 : cur
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def serialize
|
12
|
+
deflag
|
13
|
+
@alist.map { |n, f| f.serialize }.join + security_buffers.map { |n, f| f.value + (has_flag?(:UNICODE) ? "\x00".b * (f.value.length % 2) : '') }.join
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Client < Net::NTLM::Client
|
18
|
+
class Session < Net::NTLM::Client::Session
|
19
|
+
def authenticate!
|
20
|
+
calculate_user_session_key!
|
21
|
+
type3_opts = {
|
22
|
+
:lm_response => is_anonymous? ? "\x00".b : lmv2_resp,
|
23
|
+
:ntlm_response => is_anonymous? ? '' : ntlmv2_resp,
|
24
|
+
:domain => domain,
|
25
|
+
:user => username,
|
26
|
+
:workstation => workstation,
|
27
|
+
:flag => (challenge_message.flag & client.flags)
|
28
|
+
}
|
29
|
+
t3 = Net::NTLM::Message::Type3.create type3_opts
|
30
|
+
t3.extend(Message)
|
31
|
+
if negotiate_key_exchange?
|
32
|
+
t3.enable(:session_key)
|
33
|
+
rc4 = OpenSSL::Cipher.new("rc4")
|
34
|
+
rc4.encrypt
|
35
|
+
rc4.key = user_session_key
|
36
|
+
sk = rc4.update exported_session_key
|
37
|
+
sk << rc4.final
|
38
|
+
t3.session_key = sk
|
39
|
+
end
|
40
|
+
t3
|
41
|
+
end
|
42
|
+
|
43
|
+
def is_anonymous?
|
44
|
+
username == '' && password == ''
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def use_oem_strings?
|
50
|
+
# @see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/99d90ff4-957f-4c8a-80e4-5bfe5a9a9832
|
51
|
+
!challenge_message.has_flag?(:UNICODE) && challenge_message.has_flag?(:OEM)
|
52
|
+
end
|
53
|
+
|
54
|
+
def calculate_user_session_key!
|
55
|
+
if is_anonymous?
|
56
|
+
# see MS-NLMP section 3.4
|
57
|
+
@user_session_key = "\x00".b * 16
|
58
|
+
else
|
59
|
+
@user_session_key = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, nt_proof_str)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def init_context(resp = nil, channel_binding = nil)
|
65
|
+
if resp.nil?
|
66
|
+
@session = nil
|
67
|
+
type1_message
|
68
|
+
else
|
69
|
+
@session = Client::Session.new(self, Net::NTLM::Message.decode64(resp), channel_binding)
|
70
|
+
@session.authenticate!
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/ruby_smb/ntlm.rb
CHANGED
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module RubySMB
|
4
|
+
class Server
|
5
|
+
module Cli
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
allow_anonymous: true,
|
8
|
+
allow_guests: false,
|
9
|
+
domain: nil,
|
10
|
+
username: 'RubySMB',
|
11
|
+
password: 'password',
|
12
|
+
share_name: 'home',
|
13
|
+
smbv1: true,
|
14
|
+
smbv2: true,
|
15
|
+
smbv3: true
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
# Parse options from the command line. The resulting option hash is suitable for passing to {build}.
|
19
|
+
#
|
20
|
+
# @yield [options, parser] A block that can be used to update the built option parser.
|
21
|
+
# @yieldparam [Hash<Symbol => >] options The options hash that should be assigned to.
|
22
|
+
# @yieldparam [OptionParser] parser The built option parser.
|
23
|
+
# @param [Hash] defaults Default option values to use.
|
24
|
+
# @return [Hash<Symbol => >] The options hash.
|
25
|
+
# * :allow_anonymous [Boolean] Whether or not to allow anonymous authentication.
|
26
|
+
# * :allow_guest [Boolean] Whether or not to allow guest authentication.
|
27
|
+
# * :username [String] The username of the account to add for authentication.
|
28
|
+
# * :password [String] The password of the account to add for authentication.
|
29
|
+
# * :domain [String] The domain of the account to add for authentication.
|
30
|
+
# * :smbv1 [Boolean] Whether or not to enable SMBv1 dialects.
|
31
|
+
# * :smbv2 [Boolean] Whether or not to enable SMBv2 dialects.
|
32
|
+
# * :smbv3 [Boolean] Whether or not to enable SMBv3 dialects.
|
33
|
+
# * :share_name [String] The name of the share to add.
|
34
|
+
def self.parse(defaults: {}, &block)
|
35
|
+
defaults = DEFAULT_OPTIONS.merge(defaults)
|
36
|
+
options = defaults.clone
|
37
|
+
OptionParser.new do |parser|
|
38
|
+
parser.on("--share-name SHARE_NAME", "The share name (default: #{defaults[:share_name]})") do |share|
|
39
|
+
options[:share_name] = share
|
40
|
+
end
|
41
|
+
|
42
|
+
parser.on("--[no-]anonymous", "Allow anonymous access (default: #{defaults[:allow_anonymous]})") do |allow_anonymous|
|
43
|
+
options[:allow_anonymous] = allow_anonymous
|
44
|
+
end
|
45
|
+
|
46
|
+
parser.on("--[no-]guests", "Allow guest accounts (default: #{defaults[:allow_guests]})") do |allow_guests|
|
47
|
+
options[:allow_guests] = allow_guests
|
48
|
+
end
|
49
|
+
|
50
|
+
parser.on("--[no-]smbv1", "Enable or disable SMBv1 (default: #{defaults[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1|
|
51
|
+
options[:smbv1] = smbv1
|
52
|
+
end
|
53
|
+
|
54
|
+
parser.on("--[no-]smbv2", "Enable or disable SMBv2 (default: #{defaults[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2|
|
55
|
+
options[:smbv2] = smbv2
|
56
|
+
end
|
57
|
+
|
58
|
+
parser.on("--[no-]smbv3", "Enable or disable SMBv3 (default: #{defaults[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3|
|
59
|
+
options[:smbv3] = smbv3
|
60
|
+
end
|
61
|
+
|
62
|
+
parser.on("--username USERNAME", "The account's username (default: #{defaults[:username]})") do |username|
|
63
|
+
if username.include?('\\')
|
64
|
+
options[:domain], options[:username] = username.split('\\', 2)
|
65
|
+
else
|
66
|
+
options[:username] = username
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
parser.on("--password PASSWORD", "The account's password (default: #{defaults[:password]})") do |password|
|
71
|
+
options[:password] = password
|
72
|
+
end
|
73
|
+
|
74
|
+
block.call(options, parser) if block_given?
|
75
|
+
end.parse!
|
76
|
+
|
77
|
+
options
|
78
|
+
end
|
79
|
+
|
80
|
+
# Build a server instance from the specified options. The NTLM provider will be used for authentication.
|
81
|
+
#
|
82
|
+
# @param [Hash] options the options to use while building the server. See the return value of {parse} for a
|
83
|
+
# comprehensive list of keys.
|
84
|
+
def self.build(options)
|
85
|
+
ntlm_provider = RubySMB::Gss::Provider::NTLM.new(
|
86
|
+
allow_anonymous: options[:allow_anonymous],
|
87
|
+
allow_guests: options[:allow_guests]
|
88
|
+
)
|
89
|
+
ntlm_provider.put_account(options[:username], options[:password], domain: options[:domain]) # password can also be an NTLM hash
|
90
|
+
|
91
|
+
server = RubySMB::Server.new(
|
92
|
+
gss_provider: ntlm_provider,
|
93
|
+
logger: :stdout
|
94
|
+
)
|
95
|
+
server.dialects.filter! { |dialect| RubySMB::Dialect[dialect].family != RubySMB::Dialect::FAMILY_SMB1 } unless options[:smbv1]
|
96
|
+
server.dialects.filter! { |dialect| RubySMB::Dialect[dialect].family != RubySMB::Dialect::FAMILY_SMB2 } unless options[:smbv2]
|
97
|
+
server.dialects.filter! { |dialect| RubySMB::Dialect[dialect].family != RubySMB::Dialect::FAMILY_SMB3 } unless options[:smbv3]
|
98
|
+
|
99
|
+
server
|
100
|
+
end
|
101
|
+
|
102
|
+
# Run the server forever. At least 1 SMB dialect must be enabled.
|
103
|
+
#
|
104
|
+
# @param [RubySMB::Server] server The server instance to run.
|
105
|
+
# @param [#puts] out The stream to write standard output messages to.
|
106
|
+
# @param [#puts] err The stream to write error messages to.
|
107
|
+
def self.run(server, out: $stdout, err: $stderr)
|
108
|
+
if server.dialects.empty?
|
109
|
+
err.puts 'at least one version must be enabled'
|
110
|
+
return
|
111
|
+
end
|
112
|
+
|
113
|
+
out.puts 'server is running'
|
114
|
+
server.run do |server_client|
|
115
|
+
out.puts 'received connection'
|
116
|
+
true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
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
|