ruby_smb 3.1.0 → 3.1.3

Sign up to get free protection for your applications and to get access to all the features.
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