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

Instance Method Summary collapse

Constructor Details

#initializeDataStore

Initializes the data store’s internal state.



15
16
17
18
19
20
# File 'lib/msf/core/data_store.rb', line 15

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.



23
24
25
# File 'lib/msf/core/data_store.rb', line 23

def aliases
  @aliases
end

#importedObject

Returns the value of attribute imported.



24
25
26
# File 'lib/msf/core/data_store.rb', line 24

def imported
  @imported
end

#imported_byObject

Returns the value of attribute imported_by.



25
26
27
# File 'lib/msf/core/data_store.rb', line 25

def imported_by
  @imported_by
end

#optionsObject

Returns the value of attribute options.



22
23
24
# File 'lib/msf/core/data_store.rb', line 22

def options
  @options
end

Instance Method Details

#[](k) ⇒ Object

Case-insensitive wrapper around hash lookup



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

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.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/msf/core/data_store.rb', line 31

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



297
298
299
300
# File 'lib/msf/core/data_store.rb', line 297

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

#clear_non_user_definedObject

Remove all imported options from the data store.



283
284
285
286
287
288
289
290
291
292
# File 'lib/msf/core/data_store.rb', line 283

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.



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

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



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

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"


306
307
308
309
310
311
312
# File 'lib/msf/core/data_store.rb', line 306

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



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/msf/core/data_store.rb', line 317

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



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

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



145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/msf/core/data_store.rb', line 145

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



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

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.



137
138
139
140
141
# File 'lib/msf/core/data_store.rb', line 137

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.



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/msf/core/data_store.rb', line 97

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

  # Figure out the delimeter, 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 delimeter
  option_str.split(delim).each { |opt|
    var, val = opt.split('=')

    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



265
266
267
268
# File 'lib/msf/core/data_store.rb', line 265

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

#merge!(other) ⇒ Object

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



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

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



59
60
61
# File 'lib/msf/core/data_store.rb', line 59

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

#to_external_message_hObject

Hack on a hack for the external modules



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/msf/core/data_store.rb', line 183

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



208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/msf/core/data_store.rb', line 208

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)



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

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.



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

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.



77
78
79
# File 'lib/msf/core/data_store.rb', line 77

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.



274
275
276
277
278
# File 'lib/msf/core/data_store.rb', line 274

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