e25525b4a7
file:/ strings are special with some datastore options, causing them to read a file rather than emitting the exact string. This causes a couple of problems. 1. the valid? check needs to be special on assignment, since normalization really means normalizing the path, not playing with the value as we would do for other types 2. there are races or simply out-of-order assignments when running commands like 'services -p 80 -R', where the datastore option is assigned before the file is actually written. This is the 'easy' fix of disabling assignment validation (which we didn't have before anyway) for types that can expect a file:/ prefix.
326 lines
6.5 KiB
Ruby
326 lines
6.5 KiB
Ruby
# -*- coding: binary -*-
|
|
module Msf
|
|
|
|
###
|
|
#
|
|
# 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.
|
|
#
|
|
###
|
|
class DataStore < Hash
|
|
|
|
#
|
|
# Initializes the data store's internal state.
|
|
#
|
|
def initialize()
|
|
@options = Hash.new
|
|
@imported = Hash.new
|
|
@imported_by = Hash.new
|
|
end
|
|
|
|
#
|
|
# Clears the imported flag for the supplied key since it's being set
|
|
# directly.
|
|
#
|
|
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)
|
|
raise OptionValidateError.new(["Value '#{v}' is not valid for option '#{k}'"])
|
|
end
|
|
v = opt.normalize(v)
|
|
end
|
|
end
|
|
|
|
super(k,v)
|
|
end
|
|
|
|
#
|
|
# Case-insensitive wrapper around hash lookup
|
|
#
|
|
def [](k)
|
|
super(find_key_case(k))
|
|
end
|
|
|
|
#
|
|
# Case-insensitive wrapper around store
|
|
#
|
|
def store(k,v)
|
|
super(find_key_case(k), v)
|
|
end
|
|
|
|
#
|
|
# Case-insensitive wrapper around delete
|
|
#
|
|
def delete(k)
|
|
super(find_key_case(k))
|
|
end
|
|
|
|
|
|
#
|
|
# 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.
|
|
#
|
|
def update_value(k, v)
|
|
self.store(k, v)
|
|
end
|
|
|
|
#
|
|
# This method is a helper method that imports the default value for
|
|
# all of the supplied options
|
|
#
|
|
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
|
|
|
|
#
|
|
# Imports option values from a whitespace separated string in
|
|
# VAR=VAL format.
|
|
#
|
|
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
|
|
|
|
#
|
|
# Imports options from a hash and stores them in the datastore.
|
|
#
|
|
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
|
|
|
|
def import_option(key, val, imported=true, imported_by=nil, option=nil)
|
|
self.store(key, val)
|
|
|
|
@options[key] = option
|
|
@imported[key] = imported
|
|
@imported_by[key] = imported_by
|
|
end
|
|
|
|
#
|
|
# Serializes the options in the datastore to a string.
|
|
#
|
|
def to_s(delim = ' ')
|
|
str = ''
|
|
|
|
keys.sort.each { |key|
|
|
str << "#{key}=#{self[key]}" + ((str.length) ? delim : '')
|
|
}
|
|
|
|
return str
|
|
end
|
|
|
|
def to_h
|
|
datastore_hash = {}
|
|
self.keys.each do |k|
|
|
datastore_hash[k.to_s] = self[k].to_s
|
|
end
|
|
datastore_hash
|
|
end
|
|
|
|
#
|
|
# Persists the contents of the data store to a file
|
|
#
|
|
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
|
|
|
|
#
|
|
# Imports datastore values from the specified file path using the supplied
|
|
# name
|
|
#
|
|
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
|
|
|
|
#
|
|
# Returns a hash of user-defined datastore values. The returned hash does
|
|
# not include default option values.
|
|
#
|
|
def user_defined
|
|
reject { |k, v|
|
|
@imported[k] == true
|
|
}
|
|
end
|
|
|
|
#
|
|
# Remove all imported options from the data store.
|
|
#
|
|
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
|
|
|
|
#
|
|
# Completely clear all values in the hash
|
|
#
|
|
def clear
|
|
self.keys.each {|k| self.delete(k) }
|
|
self
|
|
end
|
|
|
|
#
|
|
# 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"
|
|
#
|
|
def each(&block)
|
|
list = []
|
|
self.keys.sort.each do |sidx|
|
|
list << [sidx, self[sidx]]
|
|
end
|
|
list.each(&block)
|
|
end
|
|
|
|
protected
|
|
|
|
#
|
|
# Case-insensitive key lookup
|
|
#
|
|
def find_key_case(k)
|
|
|
|
# Scan each key looking for a match
|
|
self.each_key do |rk|
|
|
if (rk.downcase == k.downcase)
|
|
return rk
|
|
end
|
|
end
|
|
|
|
# Fall through to the non-existent value
|
|
return k
|
|
end
|
|
|
|
end
|
|
|
|
###
|
|
#
|
|
# DataStore wrapper for modules that will attempt to back values against the
|
|
# framework's datastore if they aren't found in the module's datastore. This
|
|
# is done to simulate global data store values.
|
|
#
|
|
###
|
|
class ModuleDataStore < DataStore
|
|
|
|
def initialize(m)
|
|
super()
|
|
|
|
@_module = m
|
|
end
|
|
|
|
#
|
|
# Fetch the key from the local hash first, or from the framework datastore
|
|
# if we can't directly find it
|
|
#
|
|
def fetch(key)
|
|
key = find_key_case(key)
|
|
val = nil
|
|
val = super if(@imported_by[key] != 'self')
|
|
if (val.nil? and @_module and @_module.framework)
|
|
val = @_module.framework.datastore[key]
|
|
end
|
|
val = super if val.nil?
|
|
val
|
|
end
|
|
|
|
#
|
|
# Same as fetch
|
|
#
|
|
def [](key)
|
|
key = find_key_case(key)
|
|
val = nil
|
|
val = super if(@imported_by[key] != 'self')
|
|
if (val.nil? and @_module and @_module.framework)
|
|
val = @_module.framework.datastore[key]
|
|
end
|
|
val = super if val.nil?
|
|
val
|
|
end
|
|
|
|
#
|
|
# Was this entry actually set or just using its default
|
|
#
|
|
def default?(key)
|
|
(@imported_by[key] == 'self')
|
|
end
|
|
|
|
#
|
|
# Return a deep copy of this datastore.
|
|
#
|
|
def copy
|
|
clone = self.class.new(@_module)
|
|
self.keys.each do |k|
|
|
clone.import_option(k, self[k].kind_of?(String) ? self[k].dup : self[k], @imported[k], @imported_by[k])
|
|
end
|
|
clone
|
|
end
|
|
end
|
|
|
|
end
|
|
|