Class: Msf::DataStoreWithFallbacks

Inherits:
Object
  • Object
show all
Defined in:
lib/msf/core/data_store_with_fallbacks.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

ModuleDataStoreWithFallbacks

Defined Under Namespace

Classes: DataStoreSearchResult

Constant Summary collapse

GLOBAL_KEYS =

The global framework datastore doesn't currently import options For now, store an ad-hoc list of keys that the shell handles

This list could be removed if framework's bootup sequence registers these as datastore options

%w[
  ConsoleLogging
  LogLevel
  MinimumRank
  SessionLogging
  TimestampOutput
  Prompt
  PromptChar
  PromptTimeFormat
  MeterpreterPrompt
  SessionTlvLogging
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeDataStoreWithFallbacks

Initializes the data store's internal state.



33
34
35
36
37
38
39
40
41
42
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 33

def initialize
  @options     = Hash.new
  @aliases     = Hash.new

  # default values which will be referenced when not defined by the user
  @defaults = Hash.new

  # values explicitly defined, which take precedence over default values
  @user_defined = Hash.new
end

Instance Attribute Details

#aliasesHash{String => String} (protected)

Returns The key is the old option name, the value is the new option name.

Returns:

  • (Hash{String => String})

    The key is the old option name, the value is the new option name



477
478
479
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 477

def aliases
  @aliases
end

#defaultsHash{String => Msf::OptBase} (protected)

These defaults will be used if the user has not explicitly defined a specific datastore value. These will be checked as a priority to any options that also provide defaults.

Returns:

  • (Hash{String => Msf::OptBase})

    The hash of default values



474
475
476
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 474

def defaults
  @defaults
end

#optionsHash{String => Msf::OptBase}

Returns The options associated with this datastore. Used for validating values/defaults/etc.

Returns:

  • (Hash{String => Msf::OptBase})

    The options associated with this datastore. Used for validating values/defaults/etc



45
46
47
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 45

def options
  @options
end

#user_definedHash<String, Object>

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

Returns:

  • (Hash<String, Object>)

    values explicitly defined on the data store which will override any default datastore values



52
53
54
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 52

def user_defined
  @user_defined
end

Instance Method Details

#[](k) ⇒ Object

Case-insensitive wrapper around hash lookup



85
86
87
88
89
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 85

def [](k)
  search_result = search_for(k)

  search_result.value
end

#[]=(k, v) ⇒ Object

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



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 66

def []=(k, v)
  k = find_key_case(k)

  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

  @user_defined[k] = v
end

#clearObject

Completely clear all values in the data store



385
386
387
388
389
390
391
392
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 385

def clear
  self.options.clear
  self.aliases.clear
  self.defaults.clear
  self.user_defined.clear

  self
end

#copyMsf::DataStore

Return a copy of this datastore. Only string values will be duplicated, other values will share the same reference

Returns:



333
334
335
336
337
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 333

def copy
  new_instance = self.class.new
  new_instance.copy_state(self)
  new_instance
end

#copy_state(other) ⇒ Msf::DataStore (protected)

Copy the state from the other Msf::DataStore. The state will be coped in a shallow fashion, other than imported and user_defined strings.

Parameters:

Returns:



485
486
487
488
489
490
491
492
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 485

def copy_state(other)
  self.options = other.options.dup
  self.aliases = other.aliases.dup
  self.defaults = other.defaults.transform_values { |value| value.kind_of?(String) ? value.dup : value }
  self.user_defined = other.user_defined.transform_values { |value| value.kind_of?(String) ? value.dup : value }

  self
end

#default?(key) ⇒ TrueClass, FalseClass

Was this entry actually set or just using its default

Returns:

  • (TrueClass, FalseClass)


58
59
60
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 58

def default?(key)
  search_for(key).default?
end

#delete(key) ⇒ Object

Deprecated.

use ##unset instead, or set the value explicitly to nil

Parameters:

  • key (String)

    The key to search for



119
120
121
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 119

def delete(key)
  unset(key)
end

#each(&block) ⇒ Object Also known as: each_pair

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"


398
399
400
401
402
403
404
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 398

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

#each_key(&block) ⇒ Object



408
409
410
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 408

def each_key(&block)
  self.keys.each(&block)
end

#find_key_case(k) ⇒ String

Case-insensitive key lookup

Returns:

  • (String)


416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 416

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

  # Check to see if we have an exact key match - otherwise we'll have to search manually to check case sensitivity
  if @user_defined.key?(search_k) || options.key?(search_k)
    return search_k
  end

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

  # Fall through to the non-existent value
  k
end

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

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



317
318
319
320
321
322
323
324
325
326
327
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 317

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

  if ini.group?(name)
    merge!(ini[name])
  end
end

#import_defaults_from_hash(hash, imported_by:) ⇒ nil

Update defaults from a hash. These merged values are not validated by default.

Parameters:

  • hash (Hash<String, Object>)

    The default values that should be used by the datastore

  • imported_by (Object)

    Who imported the defaults, not currently used

Returns:

  • (nil)


208
209
210
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 208

def import_defaults_from_hash(hash, imported_by:)
  @defaults.merge!(hash)
end

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

Deprecated.

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



215
216
217
218
219
220
221
222
223
224
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 215

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

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

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

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



141
142
143
144
145
146
147
148
149
150
151
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 141

def import_options(options, imported_by = nil, overwrite = true)
  options.each_option do |name, option|
    if self.options[name].nil? || overwrite
      key = name
      option.aliases.each do |a|
        @aliases[a.downcase] = key.downcase
      end
      @options[key] = option
    end
  end
end

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

Deprecated.

use #merge! instead

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

Returns:

  • (nil)


199
200
201
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 199

def import_options_from_hash(option_hash, imported = true, imported_by = nil)
  merge!(option_hash)
end

#import_options_from_s(option_str, delim = nil) ⇒ Object

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



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 157

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
  }

  merge!(hash)
end

#key?(key) ⇒ TrueClass, FalseClass Also known as: has_key?, include?, member?

Returns True if the key is present in the user defined values, or within registered options. False otherwise.

Parameters:

  • key (String)

Returns:

  • (TrueClass, FalseClass)

    True if the key is present in the user defined values, or within registered options. False otherwise.



241
242
243
244
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 241

def key?(key)
  matching_key = find_key_case(key)
  keys.include?(matching_key)
end

#key_error_for(key) ⇒ Object (protected)

Raised when the specified key is not found

Parameters:

  • key (string)


496
497
498
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 496

def key_error_for(key)
  ::KeyError.new "key not found: #{key.inspect}"
end

#keysArray<String>

Returns The array of user defined datastore values, and registered option names.

Returns:

  • (Array<String>)

    The array of user defined datastore values, and registered option names



227
228
229
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 227

def keys
  (@user_defined.keys + @options.keys).uniq(&:downcase)
end

#lengthInteger Also known as: count, size

Returns The length of the registered keys.

Returns:

  • (Integer)

    The length of the registered keys



232
233
234
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 232

def length
  keys.length
end

#merge(other) ⇒ Object

Override merge to ensure we merge the aliases and imported hashes

Parameters:

  • other (Msf::Datastore, Hash)


377
378
379
380
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 377

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

#merge!(other) ⇒ Object Also known as: update

Merge the other object into the current datastore's aliases and imported hashes

Parameters:

  • other (Msf::Datastore, Hash)


343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 343

def merge!(other)
  if other.is_a?(DataStoreWithFallbacks)
    self.aliases.merge!(other.aliases)
    self.options.merge!(other.options)
    self.defaults.merge!(other.defaults)
    other.user_defined.each do |k, v|
      @user_defined[find_key_case(k)] = v
    end
  else
    other.each do |k, v|
      self.store(k, v)
    end
  end

  self
end

#remove_option(name) ⇒ nil

Removes an option and any associated value

Parameters:

  • name (String)

    the option name

Returns:

  • (nil)


128
129
130
131
132
133
134
135
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 128

def remove_option(name)
  k = find_key_case(name)
  @user_defined.delete(k)
  @aliases.delete_if { |_, v| v.casecmp?(k) }
  @options.delete_if { |option_name, _v| option_name.casecmp?(k) || option_name.casecmp?(name) }

  nil
end

#reverse_merge!(other) ⇒ Object

Reverse Merge the other object into the current datastore's aliases and imported hashes Equivalent to ActiveSupport's reverse_merge! functionality.

Parameters:

  • other (Msf::Datastore)

Raises:

  • (ArgumentError)


367
368
369
370
371
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 367

def reverse_merge!(other)
  raise ArgumentError, "invalid error type #{other.class}, expected ::Msf::DataStore" unless other.is_a?(Msf::DataStoreWithFallbacks)

  copy_state(other.merge(self))
end

#search_for(key) ⇒ DataStoreSearchResult

Search for a value within the current datastore, taking into consideration any registered aliases, fallbacks, etc.

Parameters:

  • key (String)

    The key to search for

Returns:



443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 443

def search_for(key)
  k = find_key_case(key)
  return search_result(:user_defined, @user_defined[k]) if @user_defined.key?(k)

  option = @options.fetch(k) { @options.find { |option_name, _option| option_name.casecmp?(k) }&.last }
  if option
    # If the key isn't present - check any additional fallbacks that have been registered with the option.
    # i.e. handling the scenario of SMBUser not being explicitly set, but the option has registered a more
    # generic 'Username' fallback
    option.fallbacks.each do |fallback|
    fallback_search = search_for(fallback)
      if fallback_search.found?
        return search_result(:option_fallback, fallback_search.value, fallback_key: fallback)
      end
    end
  end

  # Checking for imported default values, ignoring case again
  imported_default_match = @defaults.find { |default_key, _default_value| default_key.casecmp?(k) }
  return search_result(:imported_default, imported_default_match.last) if imported_default_match
  return search_result(:option_default, option.default) if option

  search_result(:not_found, nil)
end

#search_result(result, value, fallback_key: nil) ⇒ Object (protected)



542
543
544
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 542

def search_result(result, value, fallback_key: nil)
  DataStoreSearchResult.new(result, value, namespace: :global_data_store, fallback_key: fallback_key)
end

#store(k, v) ⇒ Object

Case-insensitive wrapper around store; Skips option validation entirely



94
95
96
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 94

def store(k,v)
  @user_defined[find_key_case(k)] = v
end

#to_external_message_hObject

Hack on a hack for the external modules



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 275

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



300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 300

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)



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

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.



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

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

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

  str
end

#unset(key) ⇒ Object

unset the current key from the datastore

Parameters:

  • key (String)

    The key to search for



109
110
111
112
113
114
115
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 109

def unset(key)
  k = find_key_case(key)
  search_result = search_for(k)
  @user_defined.delete(k)

  search_result.value
end

#update_value(k, v) ⇒ Object

Updates a value in the datastore with the specified name, k, to the specified value, v. Skips option validation entirely.



102
103
104
# File 'lib/msf/core/data_store_with_fallbacks.rb', line 102

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