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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/examples/file_server.rb +6 -61
  4. data/examples/virtual_file_server.rb +91 -0
  5. data/lib/ruby_smb/client/negotiation.rb +7 -7
  6. data/lib/ruby_smb/client.rb +2 -2
  7. data/lib/ruby_smb/fscc/file_information/file_access_information.rb +15 -0
  8. data/lib/ruby_smb/fscc/file_information/file_alignment_information.rb +45 -0
  9. data/lib/ruby_smb/fscc/file_information/file_all_information.rb +23 -0
  10. data/lib/ruby_smb/fscc/file_information/file_basic_information.rb +20 -0
  11. data/lib/ruby_smb/fscc/file_information/file_both_directory_information.rb +3 -3
  12. data/lib/ruby_smb/fscc/file_information/file_directory_information.rb +3 -3
  13. data/lib/ruby_smb/fscc/file_information/file_ea_information.rb +1 -0
  14. data/lib/ruby_smb/fscc/file_information/file_full_directory_information.rb +3 -3
  15. data/lib/ruby_smb/fscc/file_information/file_id_both_directory_information.rb +3 -3
  16. data/lib/ruby_smb/fscc/file_information/file_id_full_directory_information.rb +3 -3
  17. data/lib/ruby_smb/fscc/file_information/file_internal_information.rb +15 -0
  18. data/lib/ruby_smb/fscc/file_information/file_mode_information.rb +29 -0
  19. data/lib/ruby_smb/fscc/file_information/file_name_information.rb +16 -0
  20. data/lib/ruby_smb/fscc/file_information/file_names_information.rb +1 -1
  21. data/lib/ruby_smb/fscc/file_information/file_normalized_name_information.rb +16 -0
  22. data/lib/ruby_smb/fscc/file_information/file_position_information.rb +15 -0
  23. data/lib/ruby_smb/fscc/file_information/file_rename_information.rb +1 -1
  24. data/lib/ruby_smb/fscc/file_information/file_standard_information.rb +20 -0
  25. data/lib/ruby_smb/fscc/file_information/file_stream_information.rb +3 -0
  26. data/lib/ruby_smb/fscc/file_information.rb +41 -8
  27. data/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information.rb +1 -0
  28. data/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information.rb +1 -0
  29. data/lib/ruby_smb/fscc/file_system_information.rb +4 -0
  30. data/lib/ruby_smb/gss/provider/ntlm.rb +20 -3
  31. data/lib/ruby_smb/gss/provider.rb +10 -1
  32. data/lib/ruby_smb/ntlm/client.rb +74 -0
  33. data/lib/ruby_smb/ntlm.rb +1 -0
  34. data/lib/ruby_smb/server/cli.rb +121 -0
  35. data/lib/ruby_smb/server/server_client/session_setup.rb +4 -2
  36. data/lib/ruby_smb/server/server_client.rb +9 -1
  37. data/lib/ruby_smb/server/session.rb +5 -1
  38. data/lib/ruby_smb/server/share/provider/disk/processor/close.rb +9 -5
  39. data/lib/ruby_smb/server/share/provider/disk/processor/create.rb +2 -2
  40. data/lib/ruby_smb/server/share/provider/disk/processor/query.rb +2 -2
  41. data/lib/ruby_smb/server/share/provider/disk/processor/read.rb +15 -14
  42. data/lib/ruby_smb/server/share/provider/disk/processor.rb +76 -12
  43. data/lib/ruby_smb/server/share/provider/disk.rb +8 -2
  44. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_file.rb +85 -0
  45. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname.rb +196 -0
  46. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat.rb +175 -0
  47. data/lib/ruby_smb/server/share/provider/virtual_disk.rb +116 -0
  48. data/lib/ruby_smb/server/share/provider.rb +1 -0
  49. data/lib/ruby_smb/server.rb +14 -3
  50. data/lib/ruby_smb/smb2/tree.rb +1 -0
  51. data/lib/ruby_smb/version.rb +1 -1
  52. data/spec/lib/ruby_smb/client_spec.rb +11 -2
  53. data/spec/lib/ruby_smb/fscc/file_information/file_access_information_spec.rb +21 -0
  54. data/spec/lib/ruby_smb/fscc/file_information/file_alignment_information_spec.rb +21 -0
  55. data/spec/lib/ruby_smb/fscc/file_information/file_all_information_spec.rb +61 -0
  56. data/spec/lib/ruby_smb/fscc/file_information/file_basic_information_spec.rb +41 -0
  57. data/spec/lib/ruby_smb/fscc/file_information/file_both_directory_information_spec.rb +59 -10
  58. data/spec/lib/ruby_smb/fscc/file_information/file_directory_information_spec.rb +30 -12
  59. data/spec/lib/ruby_smb/fscc/file_information/file_ea_information_spec.rb +21 -0
  60. data/spec/lib/ruby_smb/fscc/file_information/file_full_directory_information_spec.rb +30 -12
  61. data/spec/lib/ruby_smb/fscc/file_information/file_id_both_directory_information_spec.rb +63 -10
  62. data/spec/lib/ruby_smb/fscc/file_information/file_id_full_directory_information_spec.rb +30 -12
  63. data/spec/lib/ruby_smb/fscc/file_information/file_internal_information_spec.rb +21 -0
  64. data/spec/lib/ruby_smb/fscc/file_information/file_mode_information_spec.rb +21 -0
  65. data/spec/lib/ruby_smb/fscc/file_information/file_name_information_spec.rb +44 -0
  66. data/spec/lib/ruby_smb/fscc/file_information/file_names_information_spec.rb +30 -12
  67. data/spec/lib/ruby_smb/fscc/file_information/file_network_open_information_spec.rb +51 -0
  68. data/spec/lib/ruby_smb/fscc/file_information/file_normalized_name_information_spec.rb +44 -0
  69. data/spec/lib/ruby_smb/fscc/file_information/file_position_information_spec.rb +21 -0
  70. data/spec/lib/ruby_smb/fscc/file_information/file_rename_information_spec.rb +1 -1
  71. data/spec/lib/ruby_smb/fscc/file_information/file_standard_information_spec.rb +41 -0
  72. data/spec/lib/ruby_smb/fscc/file_information/file_stream_information_spec.rb +51 -0
  73. data/spec/lib/ruby_smb/fscc/file_information_spec.rb +14 -0
  74. data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information_spec.rb +46 -0
  75. data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information_spec.rb +51 -0
  76. data/spec/lib/ruby_smb/fscc/file_system_information_spec.rb +14 -0
  77. data/spec/lib/ruby_smb/ntlm/client/session_spec.rb +114 -0
  78. data/spec/lib/ruby_smb/ntlm/client_spec.rb +36 -0
  79. data/spec/lib/ruby_smb/server/server_client_spec.rb +15 -0
  80. data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname_spec.rb +581 -0
  81. data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat_spec.rb +207 -0
  82. data/spec/lib/ruby_smb/server/share/provider/virtual_disk_spec.rb +122 -0
  83. data.tar.gz.sig +2 -2
  84. metadata +63 -2
  85. 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 @provider.allow_anonymous
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
@@ -59,3 +59,4 @@ module RubySMB
59
59
  end
60
60
  end
61
61
 
62
+ require 'ruby_smb/ntlm/client'
@@ -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] metadaa
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
- if @handles.delete(request.parameter_block.fid).nil?
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
- local_path = get_local_path(request.file_id)
24
- if local_path.nil?
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
- @handles.delete(request.file_id.to_binary_s)
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::FileNameInformation.new(file_name: @handles[request.file_id.to_binary_s].remote_path)
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
- local_path = get_local_path(request.parameter_block.fid)
12
+ handle = @handles[request.parameter_block.fid]
13
13
 
14
- if local_path.nil?
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
- buffer = nil
20
- local_path.open do |file|
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
- local_path = get_local_path(request.file_id)
37
- if local_path.nil?
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
- buffer = nil
46
- local_path.open do |file|
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