ruby_smb 3.0.6 → 3.1.2

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