ruby_smb 3.1.0 → 3.1.3
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 +0 -0
- data/examples/file_server.rb +6 -61
- data/examples/virtual_file_server.rb +91 -0
- data/lib/ruby_smb/client/negotiation.rb +7 -7
- data/lib/ruby_smb/client.rb +2 -2
- 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 +20 -3
- data/lib/ruby_smb/gss/provider.rb +10 -1
- 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/smb2/tree.rb +1 -0
- data/lib/ruby_smb/version.rb +1 -1
- data/spec/lib/ruby_smb/client_spec.rb +11 -2
- 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 +2 -2
- metadata +63 -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
|
|
@@ -78,6 +78,10 @@ module RubySMB
|
|
|
78
78
|
msg.flag |= NTLM::NEGOTIATE_FLAGS.fetch(flag)
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
+
if type1_msg.flag & NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] == NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY]
|
|
82
|
+
msg.flag |= NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY]
|
|
83
|
+
end
|
|
84
|
+
|
|
81
85
|
@server_challenge = @provider.generate_server_challenge
|
|
82
86
|
msg.challenge = @server_challenge.unpack1('Q<') # 64-bit unsigned, little endian (uint64_t)
|
|
83
87
|
target_info = Net::NTLM::TargetInfo.new('')
|
|
@@ -109,6 +113,7 @@ module RubySMB
|
|
|
109
113
|
def process_ntlm_type3(type3_msg)
|
|
110
114
|
if type3_msg.user == '' && type3_msg.domain == ''
|
|
111
115
|
if @provider.allow_anonymous
|
|
116
|
+
@session_key = "\x00".b * 16 # see MS-NLMP section 3.4
|
|
112
117
|
return WindowsError::NTStatus::STATUS_SUCCESS
|
|
113
118
|
end
|
|
114
119
|
|
|
@@ -122,6 +127,12 @@ module RubySMB
|
|
|
122
127
|
domain: type3_msg.domain
|
|
123
128
|
)
|
|
124
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
|
+
|
|
125
136
|
logger.info("NTLM authentication request failed for #{dbg_string} (no account)")
|
|
126
137
|
return WindowsError::NTStatus::STATUS_LOGON_FAILURE
|
|
127
138
|
end
|
|
@@ -229,25 +240,31 @@ module RubySMB
|
|
|
229
240
|
domain: type3_msg.domain
|
|
230
241
|
)
|
|
231
242
|
if account.nil?
|
|
232
|
-
if
|
|
243
|
+
if type3_msg.user == ''
|
|
244
|
+
is_guest = false
|
|
233
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
|
|
234
249
|
end
|
|
235
250
|
else
|
|
251
|
+
is_guest = false
|
|
236
252
|
identity = account.to_s
|
|
237
253
|
end
|
|
238
254
|
end
|
|
239
255
|
|
|
240
|
-
Result.new(buffer, nt_status, identity)
|
|
256
|
+
Result.new(buffer, nt_status, identity, is_guest)
|
|
241
257
|
end
|
|
242
258
|
end
|
|
243
259
|
|
|
244
260
|
# @param [Boolean] allow_anonymous whether or not to allow anonymous authentication attempts
|
|
245
261
|
# @param [String] default_domain the default domain to use for authentication, unless specified 'WORKGROUP' will
|
|
246
262
|
# be used
|
|
247
|
-
def initialize(allow_anonymous: false, default_domain: 'WORKGROUP')
|
|
263
|
+
def initialize(allow_anonymous: false, allow_guests: false, default_domain: 'WORKGROUP')
|
|
248
264
|
raise ArgumentError, 'Must specify a default domain' unless default_domain
|
|
249
265
|
|
|
250
266
|
@allow_anonymous = allow_anonymous
|
|
267
|
+
@allow_guests = allow_guests
|
|
251
268
|
@default_domain = default_domain
|
|
252
269
|
@accounts = []
|
|
253
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
|
|
@@ -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
|