Class: Msf::DataStore

Inherits:
Hash
  • Object
show all
Defined in:
lib/msf/core/data_store.rb

Overview

The data store is just a bitbucket that holds keyed values. It is used by various classes to hold option values and other state information.

Direct Known Subclasses

ModuleDataStore

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeDataStore

Initializes the data store’s internal state.



29
30
31
32
33
34
# File 'lib/msf/core/data_store.rb', line 29

def initialize()
  @options     = Hash.new
  @aliases     = Hash.new
  @imported    = Hash.new
  @imported_by = Hash.new
end

Instance Attribute Details

#aliasesObject

Returns the value of attribute aliases.



37
38
39
# File 'lib/msf/core/data_store.rb', line 37

def aliases
  @aliases
end

#importedObject

Returns the value of attribute imported.



38
39
40
# File 'lib/msf/core/data_store.rb', line 38

def imported
  @imported
end

#imported_byObject

Returns the value of attribute imported_by.



39
40
41
# File 'lib/msf/core/data_store.rb', line 39

def imported_by
  @imported_by
end

#optionsObject

Returns the value of attribute options.



36
37
38
# File 'lib/msf/core/data_store.rb', line 36

def options
  @options
end

Class Method Details

.newObject

Temporary forking logic for conditionally using the ModuleDatastoreWithFallbacks implementation.

This method replaces the default ‘ModuleDataStore.new` with the ability to instantiate the `ModuleDataStoreWithFallbacks` class instead, if the feature is enabled



16
17
18
19
20
21
22
23
24
# File 'lib/msf/core/data_store.rb', line 16

def self.new
  if Msf::FeatureManager.instance.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS)
    return Msf::DataStoreWithFallbacks.new
  end

  instance = allocate
  instance.send(:initialize)
  instance
end

Instance Method Details

#[](k) ⇒ Object

Case-insensitive wrapper around hash lookup



66
67
68
# File 'lib/msf/core/data_store.rb', line 66

def [](k)
  super(find_key_case(k))
end

#[]=(k, v) ⇒ Object

Clears the imported flag for the supplied key since it’s being set directly.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/msf/core/data_store.rb', line 45

def []=(k, v)
  k = find_key_case(k)
  @imported[k] = false
  @imported_by[k] = nil

  opt = @options[k]
  unless opt.nil?
    if opt.validate_on_assignment?
      unless opt.valid?(v, check_empty: false)
        raise Msf::OptionValidateError.new(["Value '#{v}' is not valid for option '#{k}'"])
      end
      v = opt.normalize(v)
    end
  end

  super(k,v)
end

#clearObject

Completely clear all values in the hash



311
312
313
314
# File 'lib/msf/core/data_store.rb', line 311

def clear
  self.keys.each {|k| self.delete(k) }
  self
end

#clear_non_user_definedObject

Remove all imported options from the data store.



297
298
299
300
301
302
303
304
305
306
# File 'lib/msf/core/data_store.rb', line 297

def clear_non_user_defined
  @imported.delete_if { |k, v|
    if (v and @imported_by[k] != 'self')
      self.delete(k)
      @imported_by.delete(k)
    end

    v
  }
end

#copyObject

Return a deep copy of this datastore.



254
255
256
257
258
259
260
261
# File 'lib/msf/core/data_store.rb', line 254

def copy
  ds = self.class.new
  self.keys.each do |k|
    ds.import_option(k, self[k].kind_of?(String) ? self[k].dup : self[k], @imported[k], @imported_by[k])
  end
  ds.aliases = self.aliases.dup
  ds
end

#delete(k) ⇒ Object

Case-insensitive wrapper around delete



80
81
82
83
# File 'lib/msf/core/data_store.rb', line 80

def delete(k)
  @aliases.delete_if { |_, v| v.casecmp(k) == 0 }
  super(find_key_case(k))
end

#each(&block) ⇒ Object

Overrides the builtin ‘each’ operator to avoid the following exception on Ruby 1.9.2+

"can't add a new key into hash during iteration"


320
321
322
323
324
325
326
# File 'lib/msf/core/data_store.rb', line 320

def each(&block)
  list = []
  self.keys.sort.each do |sidx|
    list << [sidx, self[sidx]]
  end
  list.each(&block)
end

#find_key_case(k) ⇒ Object

Case-insensitive key lookup



331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/msf/core/data_store.rb', line 331

def find_key_case(k)

  # Scan each alias looking for a key
  search_k = k.downcase
  if self.aliases.has_key?(search_k)
    search_k = self.aliases[search_k]
  end

  # Scan each key looking for a match
  self.each_key do |rk|
    if rk.casecmp(search_k) == 0
      return rk
    end
  end

  # Fall through to the non-existent value
  return k
end

#from_file(path, name = 'global') ⇒ Object

Imports datastore values from the specified file path using the supplied name



239
240
241
242
243
244
245
246
247
248
249
# File 'lib/msf/core/data_store.rb', line 239

def from_file(path, name = 'global')
  begin
    ini = Rex::Parser::Ini.from_file(path)
  rescue
    return
  end

  if (ini.group?(name))
    import_options_from_hash(ini[name], false)
  end
end

#import_option(key, val, imported = true, imported_by = nil, option = nil) ⇒ Object

TODO: Doesn’t normalize data in the same vein as: github.com/rapid7/metasploit-framework/pull/6644



159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/msf/core/data_store.rb', line 159

def import_option(key, val, imported = true, imported_by = nil, option = nil)
  self.store(key, val)

  if option
    option.aliases.each do |a|
      @aliases[a.downcase] = key.downcase
    end
  end
  @options[key] = option
  @imported[key] = imported
  @imported_by[key] = imported_by
end

#import_options(options, imported_by = nil, overwrite = false) ⇒ Object

This method is a helper method that imports the default value for all of the supplied options



99
100
101
102
103
104
105
# File 'lib/msf/core/data_store.rb', line 99

def import_options(options, imported_by = nil, overwrite = false)
  options.each_option do |name, opt|
    if self[name].nil? || overwrite
      import_option(name, opt.default, true, imported_by, opt)
    end
  end
end

#import_options_from_hash(option_hash, imported = true, imported_by = nil) ⇒ Object

Imports options from a hash and stores them in the datastore.



151
152
153
154
155
# File 'lib/msf/core/data_store.rb', line 151

def import_options_from_hash(option_hash, imported = true, imported_by = nil)
  option_hash.each_pair { |key, val|
    import_option(key, val, imported, imported_by)
  }
end

#import_options_from_s(option_str, delim = nil) ⇒ Object

Imports option values from a whitespace separated string in VAR=VAL format.



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/msf/core/data_store.rb', line 111

def import_options_from_s(option_str, delim = nil)
  hash = {}

  # Figure out the delimiter, default to space.
  if (delim.nil?)
    delim = /\s/

    if (option_str.split('=').length <= 2 or option_str.index(',') != nil)
      delim = ','
    end
  end

  # Split on the delimiter
  option_str.split(delim).each { |opt|
    var, val = opt.split('=', 2)

    next if (var =~ /^\s+$/)


    # Invalid parse?  Raise an exception and let those bastards know.
    if (var == nil or val == nil)
      var = "unknown" if (!var)

      raise Rex::ArgumentParseError, "Invalid option specified: #{var}",
        caller
    end

    # Remove trailing whitespaces from the value
    val.gsub!(/\s+$/, '')

    # Store the value
    hash[var] = val
  }

  import_options_from_hash(hash)
end

#merge(other) ⇒ Object

Override merge to ensure we merge the aliases and imported hashes



279
280
281
282
# File 'lib/msf/core/data_store.rb', line 279

def merge(other)
  ds = self.copy
  ds.merge!(other)
end

#merge!(other) ⇒ Object

Override merge! so that we merge the aliases and imported hashes



266
267
268
269
270
271
272
273
274
# File 'lib/msf/core/data_store.rb', line 266

def merge!(other)
  if other.is_a? DataStore
    self.aliases.merge!(other.aliases)
    self.imported.merge!(other.imported)
    self.imported_by.merge!(other.imported_by)
  end
  # call super last so that we return a reference to ourselves
  super
end

#store(k, v) ⇒ Object

Case-insensitive wrapper around store



73
74
75
# File 'lib/msf/core/data_store.rb', line 73

def store(k,v)
  super(find_key_case(k), v)
end

#to_external_message_hObject

Hack on a hack for the external modules



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/msf/core/data_store.rb', line 197

def to_external_message_h
  datastore_hash = {}

  array_nester = ->(arr) do
    if arr.first.is_a? Array
      arr.map &array_nester
    else
      arr.map { |item| item.to_s.dup.force_encoding('UTF-8') }
    end
  end

  self.keys.each do |k|
    # TODO arbitrary depth
    if self[k].is_a? Array
      datastore_hash[k.to_s.dup.force_encoding('UTF-8')] = array_nester.call(self[k])
    else
      datastore_hash[k.to_s.dup.force_encoding('UTF-8')] = self[k].to_s.dup.force_encoding('UTF-8')
    end
  end
  datastore_hash
end

#to_file(path, name = 'global') ⇒ Object

Persists the contents of the data store to a file



222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/msf/core/data_store.rb', line 222

def to_file(path, name = 'global')
  ini = Rex::Parser::Ini.new(path)

  ini.add_group(name)

  # Save all user-defined options to the file.
  user_defined.each_pair { |k, v|
    ini[name][k] = v
  }

  ini.to_file(path)
end

#to_hObject

Override Hash’s to_h method so we can include the original case of each key (failing to do this breaks a number of places in framework and pro that use serialized datastores)



188
189
190
191
192
193
194
# File 'lib/msf/core/data_store.rb', line 188

def to_h
  datastore_hash = {}
  self.keys.each do |k|
    datastore_hash[k.to_s] = self[k].to_s
  end
  datastore_hash
end

#to_s(delim = ' ') ⇒ Object

Serializes the options in the datastore to a string.



175
176
177
178
179
180
181
182
183
# File 'lib/msf/core/data_store.rb', line 175

def to_s(delim = ' ')
  str = ''

  keys.sort.each { |key|
    str << "#{key}=#{self[key]}" + ((str.length) ? delim : '')
  }

  return str
end

#update_value(k, v) ⇒ Object

Updates a value in the datastore with the specified name, k, to the specified value, v. This update does not alter the imported status of the value.



91
92
93
# File 'lib/msf/core/data_store.rb', line 91

def update_value(k, v)
  self.store(k, v)
end

#user_definedObject

Returns a hash of user-defined datastore values. The returned hash does not include default option values.



288
289
290
291
292
# File 'lib/msf/core/data_store.rb', line 288

def user_defined
  reject { |k, v|
    @imported[k] == true
  }
end