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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/examples/file_server.rb +8 -1
- data/examples/virtual_file_server.rb +143 -0
- data/lib/ruby_smb/client/encryption.rb +16 -4
- data/lib/ruby_smb/client/negotiation.rb +10 -8
- data/lib/ruby_smb/fscc/file_information/file_access_information.rb +15 -0
- data/lib/ruby_smb/fscc/file_information/file_alignment_information.rb +45 -0
- data/lib/ruby_smb/fscc/file_information/file_all_information.rb +23 -0
- data/lib/ruby_smb/fscc/file_information/file_basic_information.rb +20 -0
- data/lib/ruby_smb/fscc/file_information/file_both_directory_information.rb +3 -3
- data/lib/ruby_smb/fscc/file_information/file_directory_information.rb +3 -3
- data/lib/ruby_smb/fscc/file_information/file_ea_information.rb +1 -0
- data/lib/ruby_smb/fscc/file_information/file_full_directory_information.rb +3 -3
- data/lib/ruby_smb/fscc/file_information/file_id_both_directory_information.rb +3 -3
- data/lib/ruby_smb/fscc/file_information/file_id_full_directory_information.rb +3 -3
- data/lib/ruby_smb/fscc/file_information/file_internal_information.rb +15 -0
- data/lib/ruby_smb/fscc/file_information/file_mode_information.rb +29 -0
- data/lib/ruby_smb/fscc/file_information/file_name_information.rb +16 -0
- data/lib/ruby_smb/fscc/file_information/file_names_information.rb +1 -1
- data/lib/ruby_smb/fscc/file_information/file_normalized_name_information.rb +16 -0
- data/lib/ruby_smb/fscc/file_information/file_position_information.rb +15 -0
- data/lib/ruby_smb/fscc/file_information/file_rename_information.rb +1 -1
- data/lib/ruby_smb/fscc/file_information/file_standard_information.rb +20 -0
- data/lib/ruby_smb/fscc/file_information/file_stream_information.rb +3 -0
- data/lib/ruby_smb/fscc/file_information.rb +43 -6
- data/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information.rb +1 -0
- data/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information.rb +1 -0
- data/lib/ruby_smb/fscc/file_system_information.rb +4 -0
- data/lib/ruby_smb/gss/provider/ntlm.rb +20 -3
- data/lib/ruby_smb/gss/provider.rb +10 -1
- data/lib/ruby_smb/server/server_client/encryption.rb +66 -0
- data/lib/ruby_smb/server/server_client/negotiation.rb +14 -3
- data/lib/ruby_smb/server/server_client/session_setup.rb +21 -4
- data/lib/ruby_smb/server/server_client/share_io.rb +17 -0
- data/lib/ruby_smb/server/server_client/tree_connect.rb +40 -3
- data/lib/ruby_smb/server/server_client.rb +156 -38
- data/lib/ruby_smb/server/session.rb +5 -1
- data/lib/ruby_smb/server/share/provider/disk/file_system.rb +28 -0
- data/lib/ruby_smb/server/share/provider/disk/processor/close.rb +46 -0
- data/lib/ruby_smb/server/share/provider/disk/processor/create.rb +143 -0
- data/lib/ruby_smb/server/share/provider/disk/processor/query.rb +359 -0
- data/lib/ruby_smb/server/share/provider/disk/processor/read.rb +70 -0
- data/lib/ruby_smb/server/share/provider/disk/processor.rb +223 -0
- data/lib/ruby_smb/server/share/provider/disk.rb +12 -418
- data/lib/ruby_smb/server/share/provider/pipe.rb +2 -2
- data/lib/ruby_smb/server/share/provider/processor.rb +16 -0
- data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_file.rb +85 -0
- data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname.rb +196 -0
- data/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat.rb +175 -0
- data/lib/ruby_smb/server/share/provider/virtual_disk.rb +116 -0
- data/lib/ruby_smb/server/share/provider.rb +1 -0
- data/lib/ruby_smb/server.rb +13 -3
- data/lib/ruby_smb/signing.rb +18 -4
- data/lib/ruby_smb/smb1/commands.rb +1 -0
- data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +11 -1
- data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/read_andx_response.rb +5 -4
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +12 -4
- data/lib/ruby_smb/smb1/packet/trans2/data_block.rb +9 -1
- data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +52 -51
- data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +37 -37
- data/lib/ruby_smb/smb1/packet/trans2/find_information_level/find_file_both_directory_info.rb +48 -0
- data/lib/ruby_smb/smb1/packet/trans2/find_information_level.rb +28 -15
- data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +51 -51
- data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +36 -36
- data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +40 -39
- data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +40 -40
- data/lib/ruby_smb/smb1/packet/trans2/query_file_information_request.rb +60 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_file_information_response.rb +59 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level/query_fs_attribute_info.rb +31 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_level.rb +40 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request.rb +46 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response.rb +59 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_basic_info.rb +23 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_information_level/query_file_standard_info.rb +22 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_information_level.rb +62 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_path_information_request.rb +65 -0
- data/lib/ruby_smb/smb1/packet/trans2/query_path_information_response.rb +59 -0
- data/lib/ruby_smb/smb1/packet/trans2/request.rb +24 -8
- data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +4 -4
- data/lib/ruby_smb/smb1/packet/trans2/response.rb +29 -20
- data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +42 -42
- data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +23 -23
- data/lib/ruby_smb/smb1/packet/trans2/subcommands.rb +23 -5
- data/lib/ruby_smb/smb1/packet/trans2.rb +4 -0
- data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +4 -1
- data/lib/ruby_smb/smb2/negotiate_context.rb +10 -1
- data/lib/ruby_smb/smb2/packet/transform_header.rb +7 -7
- data/lib/ruby_smb/smb2/tree.rb +1 -0
- data/lib/ruby_smb/smb2.rb +1 -0
- data/lib/ruby_smb/version.rb +1 -1
- data/spec/lib/ruby_smb/client_spec.rb +31 -8
- data/spec/lib/ruby_smb/fscc/file_information/file_access_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_alignment_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_all_information_spec.rb +61 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_basic_information_spec.rb +41 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_both_directory_information_spec.rb +59 -10
- data/spec/lib/ruby_smb/fscc/file_information/file_directory_information_spec.rb +30 -12
- data/spec/lib/ruby_smb/fscc/file_information/file_ea_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_full_directory_information_spec.rb +30 -12
- data/spec/lib/ruby_smb/fscc/file_information/file_id_both_directory_information_spec.rb +63 -10
- data/spec/lib/ruby_smb/fscc/file_information/file_id_full_directory_information_spec.rb +30 -12
- data/spec/lib/ruby_smb/fscc/file_information/file_internal_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_mode_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_name_information_spec.rb +44 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_names_information_spec.rb +30 -12
- data/spec/lib/ruby_smb/fscc/file_information/file_network_open_information_spec.rb +51 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_normalized_name_information_spec.rb +44 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_position_information_spec.rb +21 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_rename_information_spec.rb +1 -1
- data/spec/lib/ruby_smb/fscc/file_information/file_standard_information_spec.rb +41 -0
- data/spec/lib/ruby_smb/fscc/file_information/file_stream_information_spec.rb +51 -0
- data/spec/lib/ruby_smb/fscc/file_information_spec.rb +14 -0
- data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_attribute_information_spec.rb +46 -0
- data/spec/lib/ruby_smb/fscc/file_system_information/file_fs_volume_information_spec.rb +51 -0
- data/spec/lib/ruby_smb/fscc/file_system_information_spec.rb +14 -0
- data/spec/lib/ruby_smb/server/server_client_spec.rb +15 -0
- data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_pathname_spec.rb +581 -0
- data/spec/lib/ruby_smb/server/share/provider/virtual_disk/virtual_stat_spec.rb +207 -0
- data/spec/lib/ruby_smb/server/share/provider/virtual_disk_spec.rb +122 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +36 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +35 -1
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_request_spec.rb +74 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_file_information_response_spec.rb +96 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_request_spec.rb +62 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_fs_information_response_spec.rb +88 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_request_spec.rb +79 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/query_path_information_response_spec.rb +96 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/response_spec.rb +3 -3
- data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_request_spec.rb +3 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb +7 -2
- data/spec/lib/ruby_smb/smb1/tree_spec.rb +3 -3
- data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +88 -2
- 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
|
data/lib/ruby_smb/server.rb
CHANGED
@@ -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
|
-
|
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,
|
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
|
data/lib/ruby_smb/signing.rb
CHANGED
@@ -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 = [
|
30
|
+
packed_sequence_counter = [sequence_counter].pack('Q<')
|
16
31
|
packet.smb_header.security_features = packed_sequence_counter
|
17
|
-
signature = OpenSSL::Digest::MD5.digest(
|
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
|
@@ -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
|
-
|
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
|
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:
|
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
|
28
|
+
# This method checks if the optional pad field is present.
|
29
29
|
def has_padding?
|
30
|
-
|
31
|
-
return
|
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)',
|
27
|
-
|
28
|
-
|
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
|
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
|