ruby_smb 3.0.6 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/examples/file_server.rb +8 -1
  4. data/examples/virtual_file_server.rb +143 -0
  5. data/lib/ruby_smb/client/encryption.rb +16 -4
  6. data/lib/ruby_smb/client/negotiation.rb +10 -8
  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 +43 -6
  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/server/server_client/encryption.rb +66 -0
  33. data/lib/ruby_smb/server/server_client/negotiation.rb +14 -3
  34. data/lib/ruby_smb/server/server_client/session_setup.rb +21 -4
  35. data/lib/ruby_smb/server/server_client/share_io.rb +17 -0
  36. data/lib/ruby_smb/server/server_client/tree_connect.rb +40 -3
  37. data/lib/ruby_smb/server/server_client.rb +156 -38
  38. data/lib/ruby_smb/server/session.rb +5 -1
  39. data/lib/ruby_smb/server/share/provider/disk/file_system.rb +28 -0
  40. data/lib/ruby_smb/server/share/provider/disk/processor/close.rb +46 -0
  41. data/lib/ruby_smb/server/share/provider/disk/processor/create.rb +143 -0
  42. data/lib/ruby_smb/server/share/provider/disk/processor/query.rb +359 -0
  43. data/lib/ruby_smb/server/share/provider/disk/processor/read.rb +70 -0
  44. data/lib/ruby_smb/server/share/provider/disk/processor.rb +223 -0
  45. data/lib/ruby_smb/server/share/provider/disk.rb +12 -418
  46. data/lib/ruby_smb/server/share/provider/pipe.rb +2 -2
  47. data/lib/ruby_smb/server/share/provider/processor.rb +16 -0
  48. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_file.rb +85 -0
  49. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname.rb +196 -0
  50. data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat.rb +175 -0
  51. data/lib/ruby_smb/server/share/provider/virtual_disk.rb +116 -0
  52. data/lib/ruby_smb/server/share/provider.rb +1 -0
  53. data/lib/ruby_smb/server.rb +13 -3
  54. data/lib/ruby_smb/signing.rb +18 -4
  55. data/lib/ruby_smb/smb1/commands.rb +1 -0
  56. data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +11 -1
  57. data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +1 -1
  58. data/lib/ruby_smb/smb1/packet/read_andx_response.rb +5 -4
  59. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +12 -4
  60. data/lib/ruby_smb/smb1/packet/trans2/data_block.rb +9 -1
  61. data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +52 -51
  62. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +37 -37
  63. data/lib/ruby_smb/smb1/packet/trans2/find_information_level/find_file_both_directory_info.rb +48 -0
  64. data/lib/ruby_smb/smb1/packet/trans2/find_information_level.rb +28 -15
  65. data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +51 -51
  66. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +36 -36
  67. data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +40 -39
  68. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +40 -40
  69. data/lib/ruby_smb/smb1/packet/trans2/query_file_information_request.rb +60 -0
  70. data/lib/ruby_smb/smb1/packet/trans2/query_file_information_response.rb +59 -0
  71. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level/query_fs_attribute_info.rb +31 -0
  72. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level.rb +40 -0
  73. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request.rb +46 -0
  74. data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response.rb +59 -0
  75. data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_basic_info.rb +23 -0
  76. data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_standard_info.rb +22 -0
  77. data/lib/ruby_smb/smb1/packet/trans2/query_information_level.rb +62 -0
  78. data/lib/ruby_smb/smb1/packet/trans2/query_path_information_request.rb +65 -0
  79. data/lib/ruby_smb/smb1/packet/trans2/query_path_information_response.rb +59 -0
  80. data/lib/ruby_smb/smb1/packet/trans2/request.rb +24 -8
  81. data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +4 -4
  82. data/lib/ruby_smb/smb1/packet/trans2/response.rb +29 -20
  83. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +42 -42
  84. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +23 -23
  85. data/lib/ruby_smb/smb1/packet/trans2/subcommands.rb +23 -5
  86. data/lib/ruby_smb/smb1/packet/trans2.rb +4 -0
  87. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +4 -1
  88. data/lib/ruby_smb/smb2/negotiate_context.rb +10 -1
  89. data/lib/ruby_smb/smb2/packet/transform_header.rb +7 -7
  90. data/lib/ruby_smb/smb2/tree.rb +1 -0
  91. data/lib/ruby_smb/smb2.rb +1 -0
  92. data/lib/ruby_smb/version.rb +1 -1
  93. data/spec/lib/ruby_smb/client_spec.rb +31 -8
  94. data/spec/lib/ruby_smb/fscc/file_information/file_access_information_spec.rb +21 -0
  95. data/spec/lib/ruby_smb/fscc/file_information/file_alignment_information_spec.rb +21 -0
  96. data/spec/lib/ruby_smb/fscc/file_information/file_all_information_spec.rb +61 -0
  97. data/spec/lib/ruby_smb/fscc/file_information/file_basic_information_spec.rb +41 -0
  98. data/spec/lib/ruby_smb/fscc/file_information/file_both_directory_information_spec.rb +59 -10
  99. data/spec/lib/ruby_smb/fscc/file_information/file_directory_information_spec.rb +30 -12
  100. data/spec/lib/ruby_smb/fscc/file_information/file_ea_information_spec.rb +21 -0
  101. data/spec/lib/ruby_smb/fscc/file_information/file_full_directory_information_spec.rb +30 -12
  102. data/spec/lib/ruby_smb/fscc/file_information/file_id_both_directory_information_spec.rb +63 -10
  103. data/spec/lib/ruby_smb/fscc/file_information/file_id_full_directory_information_spec.rb +30 -12
  104. data/spec/lib/ruby_smb/fscc/file_information/file_internal_information_spec.rb +21 -0
  105. data/spec/lib/ruby_smb/fscc/file_information/file_mode_information_spec.rb +21 -0
  106. data/spec/lib/ruby_smb/fscc/file_information/file_name_information_spec.rb +44 -0
  107. data/spec/lib/ruby_smb/fscc/file_information/file_names_information_spec.rb +30 -12
  108. data/spec/lib/ruby_smb/fscc/file_information/file_network_open_information_spec.rb +51 -0
  109. data/spec/lib/ruby_smb/fscc/file_information/file_normalized_name_information_spec.rb +44 -0
  110. data/spec/lib/ruby_smb/fscc/file_information/file_position_information_spec.rb +21 -0
  111. data/spec/lib/ruby_smb/fscc/file_information/file_rename_information_spec.rb +1 -1
  112. data/spec/lib/ruby_smb/fscc/file_information/file_standard_information_spec.rb +41 -0
  113. data/spec/lib/ruby_smb/fscc/file_information/file_stream_information_spec.rb +51 -0
  114. data/spec/lib/ruby_smb/fscc/file_information_spec.rb +14 -0
  115. data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information_spec.rb +46 -0
  116. data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information_spec.rb +51 -0
  117. data/spec/lib/ruby_smb/fscc/file_system_information_spec.rb +14 -0
  118. data/spec/lib/ruby_smb/server/server_client_spec.rb +15 -0
  119. data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname_spec.rb +581 -0
  120. data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat_spec.rb +207 -0
  121. data/spec/lib/ruby_smb/server/share/provider/virtual_disk_spec.rb +122 -0
  122. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_request_spec.rb +2 -2
  123. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +36 -2
  124. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_request_spec.rb +2 -2
  125. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +35 -1
  126. data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_request_spec.rb +74 -0
  127. data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_response_spec.rb +96 -0
  128. data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request_spec.rb +62 -0
  129. data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response_spec.rb +88 -0
  130. data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_request_spec.rb +79 -0
  131. data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_response_spec.rb +96 -0
  132. data/spec/lib/ruby_smb/smb1/packet/trans2/request_spec.rb +2 -2
  133. data/spec/lib/ruby_smb/smb1/packet/trans2/response_spec.rb +3 -3
  134. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_request_spec.rb +3 -2
  135. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb +7 -2
  136. data/spec/lib/ruby_smb/smb1/tree_spec.rb +3 -3
  137. data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +2 -2
  138. data.tar.gz.sig +0 -0
  139. metadata +88 -2
  140. metadata.gz.sig +0 -0
@@ -0,0 +1,196 @@
1
+ module RubySMB
2
+ class Server
3
+ module Share
4
+ module Provider
5
+ class VirtualDisk < Disk
6
+ # This object emulates Ruby's builtin Pathname object but uses a virtual file system instead of the real local
7
+ # one.
8
+ class VirtualPathname
9
+ SEPARATOR = File::SEPARATOR
10
+ # see: https://ruby-doc.org/stdlib-3.1.1/libdoc/pathname/rdoc/Pathname.html
11
+ STAT_METHODS = %i[
12
+ atime
13
+ birthtime
14
+ blockdev?
15
+ chardev?
16
+ ctime
17
+ directory?
18
+ executable?
19
+ file?
20
+ ftype
21
+ grpowned?
22
+ mtime
23
+ owned?
24
+ pipe?
25
+ readable?
26
+ setgid?
27
+ setuid?
28
+ size
29
+ socket?
30
+ sticky?
31
+ symlink?
32
+ world_readable?
33
+ world_writable?
34
+ writable?
35
+ zero?
36
+ ]
37
+ private_constant :STAT_METHODS
38
+
39
+ attr_accessor :virtual_disk
40
+
41
+ # @param [Hash] disk The mapping of paths to objects representing the virtual file system.
42
+ # @param [String] path The path of this entry.
43
+ # @param [Boolean] exist? Whether or not this entry represents an existing entry in the virtual file system.
44
+ # @param [File::Stat] stat An explicit stat that represents this object. A default VirtualStat will be
45
+ # created unless specified.
46
+ def initialize(disk, path, **kwargs)
47
+ @virtual_disk = disk
48
+ @path = path
49
+
50
+ if kwargs.fetch(:exist?, true)
51
+ if kwargs[:stat]
52
+ if kwargs[:stat].is_a?(Hash)
53
+ @stat = VirtualStat.new(**kwargs[:stat])
54
+ else
55
+ @stat = kwargs[:stat]
56
+ end
57
+ else
58
+ @stat = VirtualStat.new
59
+ end
60
+ else
61
+ raise ArgumentError.new('can not specify a stat object when exist? is false') if kwargs[:stat]
62
+ @stat = nil
63
+ end
64
+ end
65
+
66
+ def ==(other)
67
+ other.is_a?(self.class) && other.to_s == to_s
68
+ end
69
+
70
+ def <=>(other)
71
+ to_s <=> other.to_s
72
+ end
73
+
74
+ def exist?
75
+ !@stat.nil?
76
+ rescue Errno::ENOENT
77
+ false
78
+ end
79
+
80
+ def stat
81
+ raise Errno::ENOENT.new('No such file or directory') unless exist? && (@stat.file? || @stat.directory?)
82
+
83
+ @stat
84
+ end
85
+
86
+ def join(other)
87
+ # per the docs this Pathname#join doesn't touch the file system
88
+ # see: https://ruby-doc.org/stdlib-3.1.1/libdoc/pathname/rdoc/Pathname.html#class-Pathname-label-Core+methods
89
+ lookup_or_create(Pathname.new(to_s).join(other).to_s)
90
+ end
91
+
92
+ alias :+ :join
93
+ alias :/ :join
94
+
95
+ def to_s
96
+ @path
97
+ end
98
+
99
+ def absolute?
100
+ to_s.start_with?(SEPARATOR)
101
+ end
102
+
103
+ def relative?
104
+ !absolute?
105
+ end
106
+
107
+ def basename
108
+ lookup_or_create(self.class.basename(to_s))
109
+ end
110
+
111
+ def self.basename(*args)
112
+ File.basename(*args)
113
+ end
114
+
115
+ def dirname
116
+ lookup_or_create(self.class.dirname(to_s))
117
+ end
118
+
119
+ def self.dirname(*args)
120
+ File.dirname(*args)
121
+ end
122
+
123
+ def extname
124
+ File.extname(to_s)
125
+ end
126
+
127
+ def split
128
+ [dirname, basename]
129
+ end
130
+
131
+ alias :parent :dirname
132
+
133
+ def children(with_directory=true)
134
+ raise Errno::ENOTDIR.new("Not a directory @ dir_initialize - #{to_s}") unless directory?
135
+
136
+ @virtual_disk.each_value.select { |dent| dent.dirname == self && dent != self }.map { |dent| with_directory ? dent : dent.basename }
137
+ end
138
+
139
+ def entries
140
+ children(false)
141
+ end
142
+
143
+ def cleanpath(consider_symlink=false)
144
+ lookup_or_create(self.class.cleanpath(to_s), stat: (exist? ? stat : nil))
145
+ end
146
+
147
+ def self.cleanpath(path)
148
+ # per the docs this Pathname#cleanpath doesn't touch the file system
149
+ # see: https://ruby-doc.org/stdlib-3.1.1/libdoc/pathname/rdoc/Pathname.html#class-Pathname-label-Core+methods
150
+ Pathname.new(path).cleanpath.to_s
151
+ end
152
+
153
+ private
154
+
155
+ # Check the virtual file system to see if the entry exists. Return it if it does, otherwise create a new
156
+ # entry representing a non-existent path.
157
+ #
158
+ # @param [String] path The path to lookup in the virtual file system. It will be normalized using #cleanpath.
159
+ # @return [Pathname] The path object representing the specified string.
160
+ def lookup_or_create(path, **kwargs)
161
+ existing = @virtual_disk[self.class.cleanpath(path)]
162
+ return existing if existing
163
+
164
+ kwargs[:exist?] = false
165
+ VirtualPathname.new(@virtual_disk, path, **kwargs)
166
+ end
167
+
168
+ def method_missing(symbol, *args)
169
+ # should we forward to one of the stat methods
170
+ if STAT_METHODS.include?(symbol)
171
+ # if we have a stat object then forward it
172
+ return stat.send(symbol, *args) if exist?
173
+ # if we don't have a stat object, emulate what Pathname does when it does not exist
174
+
175
+ # these two methods return nil
176
+ return nil if %i[ world_readable? world_writable? ].include?(symbol)
177
+
178
+ # any of the other ?-suffixed methods return false
179
+ return false if symbol.to_s.end_with?('?')
180
+
181
+ # any other method raises a Errno::ENOENT exception
182
+ raise Errno::ENOENT.new('No such file or directory')
183
+ end
184
+
185
+ raise NoMethodError, "undefined method `#{symbol}' for #{self.class}"
186
+ end
187
+
188
+ def respond_to_missing?(symbol, include_private = false)
189
+ STAT_METHODS.include?(symbol)
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,175 @@
1
+ module RubySMB
2
+ class Server
3
+ module Share
4
+ module Provider
5
+ class VirtualDisk < Disk
6
+ # This object emulates Ruby's builtin File::Stat object but uses a virtual file system instead of the real
7
+ # local one. The current implementation is limited to only representing directories and standard files. All
8
+ # attributes are read-only and once the object is created, it is immutable.
9
+ class VirtualStat
10
+
11
+ # All of the keyword arguments are the keys of the attributes to set. The names are left as is, maintaining
12
+ # a direct 1 to 1 relationship. See the Ruby docs for File::Stat
13
+ # (https://ruby-doc.org/core-3.0.2/File/Stat.html) for a list of all the attributes that can be set. Some
14
+ # values are calculated based on others such as the mode readable? being calculated based on the mode.
15
+ def initialize(**kwargs)
16
+ # directory and file both default to being the opposite of each other, one or both can be specified
17
+ # but they can not both be true at the same time
18
+ is_dir = !!kwargs.fetch(:directory?, !kwargs.fetch(:file?, false)) # defaults to not file which defaults to false
19
+ is_fil = !!kwargs.fetch(:file?, !kwargs.fetch(:directory?, true)) # defaults to not directory which defaults to true
20
+ raise ArgumentError.new('must be either a file or a directory') unless is_dir ^ is_fil
21
+
22
+ @values = kwargs.dup
23
+ # the default is a directory
24
+ @values[:directory?] = !@values.delete(:file?) if @values.key?(:file?) # normalize on directory? if file? was specified.
25
+
26
+ @birthtime = kwargs[:birthtime] || Time.now
27
+ end
28
+
29
+ def blksize
30
+ @values.fetch(:blksize, 4096)
31
+ end
32
+
33
+ def blockdev?
34
+ false
35
+ end
36
+
37
+ def blocks
38
+ @values.fetch(:blocks, 0)
39
+ end
40
+
41
+ def chardev?
42
+ false
43
+ end
44
+
45
+ def pipe?
46
+ false
47
+ end
48
+
49
+ def socket?
50
+ false
51
+ end
52
+
53
+ def symlink?
54
+ false
55
+ end
56
+
57
+ def directory?
58
+ @values.fetch(:directory?, true)
59
+ end
60
+
61
+ def file?
62
+ !directory?
63
+ end
64
+
65
+ def ftype
66
+ raise Errno::ENOENT.new('No such file or directory') unless file? || directory?
67
+
68
+ file? ? 'file' : 'directory'
69
+ end
70
+
71
+ def size
72
+ @values.fetch(:size, 0)
73
+ end
74
+
75
+ def zero?
76
+ file? && size == 0
77
+ end
78
+
79
+ def nlink
80
+ @values.fetch(:nlink, 0)
81
+ end
82
+
83
+ def dev
84
+ @values[:dev] ||= rand(1..0xfe)
85
+ end
86
+
87
+ def ino
88
+ @values[:ino] ||= rand(1..0xfffe)
89
+ end
90
+
91
+ def gid
92
+ @values.fetch(:gid, Process.gid)
93
+ end
94
+
95
+ def grpowned?
96
+ gid == Process.gid
97
+ end
98
+
99
+ def uid
100
+ @values.fetch(:uid, Process.uid)
101
+ end
102
+
103
+ def owned?
104
+ uid == Process.uid
105
+ end
106
+
107
+ # last access time
108
+ def atime
109
+ @values.fetch(:atime, @birthtime)
110
+ end
111
+
112
+ # modification time
113
+ def mtime
114
+ @values.fetch(:mtime, @birthtime)
115
+ end
116
+
117
+ # change time
118
+ def ctime
119
+ @values.fetch(:ctime, @birthtime)
120
+ end
121
+
122
+ # the permission bits, normalized based on the standard GNU representation,
123
+ # see: https://www.gnu.org/software/libc/manual/html_node/Permission-Bits.html
124
+ def mode
125
+ @values.fetch(:mode, (file? ? 0o644 : 0o755))
126
+ end
127
+
128
+ def setuid?
129
+ mode & 0o04000 != 0
130
+ end
131
+
132
+ def setgid?
133
+ mode & 0o02000 != 0
134
+ end
135
+
136
+ def sticky?
137
+ mode & 0o01000 != 0
138
+ end
139
+
140
+ def readable?
141
+ return true if owned? && (mode & 1 << 8 != 0)
142
+ return true if grpowned? && (mode & 1 << 5 != 0)
143
+ return true if world_readable?
144
+ return false
145
+ end
146
+
147
+ def world_readable?
148
+ mode & 1 << 2 != 0
149
+ end
150
+
151
+ def writable?
152
+ return true if owned? && (mode & 1 << 7 != 0)
153
+ return true if grpowned? && (mode & 1 << 4 != 0)
154
+ return true if world_writable?
155
+ return false
156
+ end
157
+
158
+ def world_writable?
159
+ mode & 1 << 1 != 0
160
+ end
161
+
162
+ def executable?
163
+ return true if owned? && (mode & 1 << 6 != 0)
164
+ return true if grpowned? && (mode & 1 << 3 != 0)
165
+ return true if mode & 1 != 0
166
+ return false
167
+ end
168
+
169
+ attr_reader :birthtime
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,116 @@
1
+ require 'ruby_smb/server/share/provider/disk'
2
+ require 'ruby_smb/server/share/provider/virtual_disk/virtual_file'
3
+ require 'ruby_smb/server/share/provider/virtual_disk/virtual_pathname'
4
+ require 'ruby_smb/server/share/provider/virtual_disk/virtual_stat'
5
+
6
+ module RubySMB
7
+ class Server
8
+ module Share
9
+ module Provider
10
+ # This is a share provider that exposes a virtual file system whose entries do not exist on disk.
11
+ # @since 3.1.1
12
+ class VirtualDisk < Disk
13
+ HASH_METHODS = %i[ [] each each_key each_value keys length values ]
14
+ private_constant :HASH_METHODS
15
+
16
+ # @param [String] name The name of this share.
17
+ def initialize(name)
18
+ @vfs = {}
19
+ super(name, add(VirtualPathname.new(self, File::SEPARATOR)))
20
+ end
21
+
22
+ # Add a dynamic file to the virtual file system. A dynamic file is one whose contents are generated by the
23
+ # specified block. The contents are generated when the share is opened.
24
+ #
25
+ # @param [String] path The path of the file to add, relative to the share root.
26
+ # @param [File::Stat] stat An explicit stat object describing the file.
27
+ # @yield The content generation routine.
28
+ # @yieldreturn [String] The generated file content.
29
+ def add_dynamic_file(path, stat: nil, &block)
30
+ raise ArgumentError.new('a block must be specified for dynamic files') unless block_given?
31
+ path = VirtualPathname.cleanpath(path)
32
+ path = File::SEPARATOR + path unless path.start_with?(File::SEPARATOR)
33
+ raise ArgumentError.new('must be a file') if stat && !stat.file?
34
+
35
+ vf = VirtualDynamicFile.new(self, path, stat: stat, &block)
36
+ add(vf)
37
+ end
38
+
39
+ # Add a mapped file to the virtual file system. A mapped file is one who is backed by an entry on
40
+ # disk. The path need not be present, but if it does exist, it must be a file.
41
+ #
42
+ # @param [String] path The path of the file to add, relative to the share root.
43
+ # @param [String, Pathname] mapped_path The path on the local file system to map into the virtual file system.
44
+ def add_mapped_file(path, mapped_path)
45
+ path = VirtualPathname.cleanpath(path)
46
+ path = File::SEPARATOR + path unless path.start_with?(File::SEPARATOR)
47
+
48
+ vf = VirtualMappedFile.new(self, path, mapped_path)
49
+ add(vf)
50
+ end
51
+
52
+ # Add a static file to the virtual file system. A static file is one whose contents are known at creation time
53
+ # and do not change. If *content* is a file-like object that responds to #read, the data will be read using
54
+ # that method. Likewise, if *content* has a #stat attribute and *stat* was not specified, then the value of
55
+ # content's #stat attribute will be used.
56
+ #
57
+ # @param [String] path The path of the file to add, relative to the share root.
58
+ # @param [String, #read, #stat] content The static content to add.
59
+ # @param [File::Stat] stat An explicit stat object describing the file.
60
+ def add_static_file(path, content, stat: nil)
61
+ path = VirtualPathname.cleanpath(path)
62
+ path = File::SEPARATOR + path unless path.start_with?(File::SEPARATOR)
63
+ raise ArgumentError.new('must be a file') if stat && !stat.file?
64
+
65
+ stat = content.stat if content.respond_to?(:stat) && stat.nil?
66
+ content = content.read if content.respond_to?(:read)
67
+ vf = VirtualStaticFile.new(self, path, content, stat: stat)
68
+ add(vf)
69
+ end
70
+
71
+ def add(virtual_pathname)
72
+ raise ArgumentError.new('paths must be absolute') unless virtual_pathname.absolute?
73
+
74
+ path = virtual_pathname.to_s
75
+ raise ArgumentError.new('paths must be normalized') unless VirtualPathname.cleanpath(path) == path
76
+
77
+ path_parts = path.split(VirtualPathname::SEPARATOR)
78
+ 2.upto(path_parts.length - 1) do |idx|
79
+ ancestor = path_parts[0...idx].join(path[VirtualPathname::SEPARATOR])
80
+ next if @vfs[ancestor]&.directory?
81
+
82
+ @vfs[ancestor] = VirtualPathname.new(self, ancestor, stat: VirtualStat.new(directory?: true))
83
+ end
84
+
85
+ @vfs[path] = virtual_pathname
86
+ end
87
+
88
+ def new_processor(server_client, session)
89
+ scoped_virtual_disk = self.class.new(@name)
90
+ @vfs.each_value do |path|
91
+ path = path.dup
92
+ path.virtual_disk = scoped_virtual_disk if path.is_a?(VirtualPathname)
93
+ path = path.generate(server_client, session) if path.is_a?(VirtualDynamicFile)
94
+ scoped_virtual_disk.add(path)
95
+ end
96
+ self.class::Processor.new(scoped_virtual_disk, server_client, session)
97
+ end
98
+
99
+ private
100
+
101
+ def method_missing(symbol, *args)
102
+ if HASH_METHODS.include?(symbol)
103
+ return @vfs.send(symbol, *args)
104
+ end
105
+
106
+ raise NoMethodError, "undefined method `#{symbol}' for #{self.class}"
107
+ end
108
+
109
+ def respond_to_missing?(symbol, include_private = false)
110
+ HASH_METHODS.include?(symbol)
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -36,3 +36,4 @@ end
36
36
 
37
37
  require 'ruby_smb/server/share/provider/disk'
38
38
  require 'ruby_smb/server/share/provider/pipe'
39
+ require 'ruby_smb/server/share/provider/virtual_disk'
@@ -14,8 +14,10 @@ module RubySMB
14
14
  Connection = Struct.new(:client, :thread)
15
15
 
16
16
  # @param server_sock the socket on which the server should listen
17
- # @param [Gss::Provider] the authentication provider
18
- def initialize(server_sock: nil, gss_provider: nil, logger: nil)
17
+ # @param [Gss::Provider] gss_provider the authentication provider
18
+ # @param [::Logger] logger the logger to use for diagnostic messages
19
+ # @param thread_factory a block to create threads for serving clients
20
+ def initialize(server_sock: nil, gss_provider: nil, logger: nil, thread_factory: nil)
19
21
  server_sock = ::TCPServer.new(445) if server_sock.nil?
20
22
 
21
23
  @guid = Random.new.bytes(16)
@@ -36,6 +38,14 @@ module RubySMB
36
38
  @logger = logger
37
39
  end
38
40
 
41
+ if thread_factory.nil?
42
+ # the default thread factory uses Ruby's standard Thread#new
43
+ thread_factory = Proc.new do |_server_client, &block|
44
+ Thread.new(&block)
45
+ end
46
+ end
47
+ @thread_factory = thread_factory
48
+
39
49
  # share name => provider instance
40
50
  @shares = {
41
51
  'IPC$' => Share::Provider::IpcPipe.new
@@ -53,7 +63,7 @@ module RubySMB
53
63
  loop do
54
64
  sock = @socket.accept
55
65
  server_client = ServerClient.new(self, RubySMB::Dispatcher::Socket.new(sock, read_timeout: nil))
56
- @connections << Connection.new(server_client, Thread.new { server_client.run })
66
+ @connections << Connection.new(server_client, @thread_factory.call(server_client) { server_client.run })
57
67
 
58
68
  break unless block.nil? || block.call(server_client)
59
69
  end
@@ -6,17 +6,31 @@ module RubySMB
6
6
  # @return [String]
7
7
  attr_accessor :session_key
8
8
 
9
- # Take an SMB1 packet and sign it.
9
+ # Take an SMB1 packet and sign it. This version is an instance method that
10
+ # accesses the necessary values from the object instance.
10
11
  #
11
12
  # @param [RubySMB::GenericPacket] packet The packet to sign.
12
13
  # @return [RubySMB::GenericPacket] the signed packet
13
14
  def smb1_sign(packet)
15
+ packet = Signing::smb1_sign(packet, @session_key, @sequence_counter)
16
+ @sequence_counter += 1
17
+
18
+ packet
19
+ end
20
+
21
+ # Take an SMB1 packet and sign it. This version is a module function that
22
+ # requires the necessary values to be explicitly passed to it.
23
+ #
24
+ # @param [RubySMB::GenericPacket] packet The packet to sign.
25
+ # @param [String] session_key The key to use for signing.
26
+ # @param [Integer] sequence_counter The sequence counter of packet to be sent.
27
+ # @return [RubySMB::GenericPacket] the signed packet
28
+ def self.smb1_sign(packet, session_key, sequence_counter)
14
29
  # Pack the Sequence counter into a int64le
15
- packed_sequence_counter = [@sequence_counter].pack('Q<')
30
+ packed_sequence_counter = [sequence_counter].pack('Q<')
16
31
  packet.smb_header.security_features = packed_sequence_counter
17
- signature = OpenSSL::Digest::MD5.digest(@session_key + packet.to_binary_s)[0, 8]
32
+ signature = OpenSSL::Digest::MD5.digest(session_key + packet.to_binary_s)[0, 8]
18
33
  packet.smb_header.security_features = signature
19
- @sequence_counter += 1
20
34
 
21
35
  packet
22
36
  end
@@ -12,6 +12,7 @@ module RubySMB
12
12
  SMB_COM_NEGOTIATE = 0x72
13
13
  SMB_COM_SESSION_SETUP_ANDX = 0x73
14
14
  SMB_COM_LOGOFF = 0x74
15
+ SMB_COM_LOGOFF_ANDX = 0x74
15
16
  SMB_COM_TREE_CONNECT = 0x75
16
17
  SMB_COM_NT_TRANSACT = 0xA0
17
18
  SMB_COM_NT_TRANSACT_SECONDARY = 0xA1
@@ -47,7 +47,17 @@ module RubySMB
47
47
 
48
48
  # Represents the specific layout of the DataBlock for a {NtCreateAndxRequest} Packet.
49
49
  class DataBlock < RubySMB::SMB1::DataBlock
50
- string :file_name, label: 'File Name'
50
+ uint8 :pad, label: 'Pad', onlyif: :has_padding?
51
+ choice :file_name, selection: -> { parent.smb_header.flags2.unicode } do
52
+ stringz 0
53
+ stringz16 1
54
+ end
55
+
56
+ # This method checks if the optional pad field is present.
57
+ def has_padding?
58
+ parent.smb_header.flags2.unicode == 1 && pad.abs_offset % 2 == 1
59
+ end
60
+ private :has_padding?
51
61
  end
52
62
 
53
63
  smb_header :smb_header
@@ -57,7 +57,7 @@ module RubySMB
57
57
  end
58
58
  end
59
59
 
60
- # The Trans2 Data Blcok for this particular Subcommand
60
+ # The Trans2 Data Block for this particular Subcommand
61
61
  class Trans2Data < BinData::Record
62
62
  security_descriptor :security_descriptor
63
63
  file_full_ea_info :extended_attributes
@@ -22,13 +22,14 @@ module RubySMB
22
22
 
23
23
  # Represents the specific layout of the DataBlock for a {ReadAndxResponse} Packet.
24
24
  class DataBlock < RubySMB::SMB1::DataBlock
25
- uint8 :pad, label: 'Pad', onlyif: -> { has_padding? }
25
+ uint8 :pad, label: 'Pad', onlyif: :has_padding?
26
26
  string :data, label: 'Data', read_length: -> { parent.parameter_block.data_length }
27
27
 
28
- # This method checks if the optional pad field is present in the response.
28
+ # This method checks if the optional pad field is present.
29
29
  def has_padding?
30
- return false if byte_count.zero?
31
- return true if byte_count - parent.parameter_block.data_length == 1
30
+ bc = byte_count.clear? ? 0 : byte_count # work around infinite recursion
31
+ return false if bc == 0
32
+ return true if bc - parent.parameter_block.data_length == 1
32
33
  false
33
34
  end
34
35
  private :has_padding?
@@ -23,9 +23,17 @@ module RubySMB
23
23
  # for the security blob, you must null-terminate the {native_os} and {native_lan_man} fields
24
24
  # yourself if you set them away from their defaults.
25
25
  class DataBlock < RubySMB::SMB1::DataBlock
26
- string :security_blob, label: 'Security Blob (GSS-API)', length: -> { parent.parameter_block.security_blob_length }
27
- string :native_os, label: 'Native OS', initial_value: "Windows 7 Ultimate N 7601 Service Pack 1\x00"
28
- string :native_lan_man, label: 'Native LAN Manager', initial_value: "Windows 7 Ultimate N 6.1\x00"
26
+ string :security_blob, label: 'Security Blob (GSS-API)', read_length: -> { parent.parameter_block.security_blob_length }
27
+ uint8 :pad1, label: 'Pad 1', onlyif: -> { parent.smb_header.flags2.unicode == 1 && pad1.abs_offset % 2 == 1 }
28
+ choice :native_os, label: 'Native OS', selection: -> { parent.smb_header.flags2.unicode } do
29
+ stringz 0, initial_value: 'Windows 7 Ultimate N 7601 Service Pack 1'
30
+ stringz16 1, initial_value: 'Windows 7 Ultimate N 7601 Service Pack 1'.encode('utf-16le')
31
+ end
32
+ uint8 :pad2, label: 'Pad 2', onlyif: -> { parent.smb_header.flags2.unicode == 1 && pad2.abs_offset % 2 == 1 }
33
+ choice :native_lan_man, label: 'Native LAN Manager', selection: -> { parent.smb_header.flags2.unicode } do
34
+ stringz 0, initial_value: 'Windows 7 Ultimate N 6.1'
35
+ stringz16 1, initial_value: 'Windows 7 Ultimate N 6.1'.encode('utf-16le')
36
+ end
29
37
  end
30
38
 
31
39
  smb_header :smb_header
@@ -34,7 +42,7 @@ module RubySMB
34
42
 
35
43
  # Takes an NTLM Type 1 Message and creates the GSS Security Blob
36
44
  # for it and sets it in the {RubySMB::SMB1::Packet::SessionSetupRequest::DataBlock#security_blob}
37
- # field. It also automaticaly sets the length in
45
+ # field. It also automatically sets the length in
38
46
  # {RubySMB::SMB1::Packet::SessionSetupRequest::ParameterBlock#security_blob_length}
39
47
  #
40
48
  # @param type1_message [String] the serialized Type 1 NTLM message
@@ -35,13 +35,21 @@ module RubySMB
35
35
  # Determines the correct length for the padding in front of
36
36
  # #trans2_data. It should always force a 4-byte alignment.
37
37
  def pad2_length
38
- if enable_padding
38
+ if enable_padding && (trans2_data.num_bytes > 0 || (!byte_count.clear? && offset_of(pad2) - byte_count.num_bytes < byte_count))
39
39
  offset = (trans2_parameters.abs_offset + trans2_parameters.length) % 4
40
40
  (4 - offset) % 4
41
41
  else
42
42
  0
43
43
  end
44
44
  end
45
+
46
+ # Some structures use an opaque buffer in trans2_data. Calculate its
47
+ # size here.
48
+ def buffer_read_length
49
+ return 0 if byte_count.clear?
50
+
51
+ byte_count + byte_count.num_bytes - offset_of(trans2_data)
52
+ end
45
53
  end
46
54
  end
47
55
  end