lib/metasploit/framework/data_service/proxy/core.rb
require 'open3'
require 'metasploit/framework/data_service/proxy/data_proxy_auto_loader'
#
# Holds references to data services (@see Metasploit::Framework::DataService)
# and forwards data to the implementation set as current.
#
module Metasploit
module Framework
module DataService
class DataProxy
include DataProxyAutoLoader
attr_reader :usable
def initialize(opts = {})
@data_services = {}
@data_service_id = 0
@usable = false
setup(opts)
end
#
# Returns current error state
#
def error
return @error if (@error)
return @current_data_service.error if @current_data_service && !@current_data_service.error.nil?
return 'unknown'
end
def is_local?
if @current_data_service
return @current_data_service.is_local?
end
return false
end
#
# Determines if the data service is active
#
def active
if @current_data_service
return @current_data_service.active
end
return false
end
#
# Registers the specified data service with the proxy
# and immediately sets it as the primary if active
#
def register_data_service(data_service)
validate(data_service)
data_service_id = @data_service_id += 1
@data_services[data_service_id] = data_service
set_data_service(data_service_id)
end
#
# Delete the specified data service
#
def delete_data_service(data_service_id)
raise ArgumentError.new('Cannot delete data service id: 1') if data_service_id.to_i == 1
data_service = @data_services.delete(data_service_id.to_i)
if data_service.nil?
raise "Data service with id: #{data_service_id} does not exist"
end
if @current_data_service == data_service
# set the current data service to the first data service created
@current_data_service = @data_services[1]
end
end
def delete_current_data_service
@data_services.each do |id, ds|
if ds == @current_data_service
if id == 1
raise "Unable to delete the local data service. Please use db_disconnect."
else
@data_services.delete(id)
@current_data_service = @data_services[1]
end
end
end
end
#
# Set the data service to be used
#
def set_data_service(data_service_id)
data_service = @data_services[data_service_id.to_i]
if data_service.nil?
raise "Data service with id: #{data_service_id} does not exist"
end
if !data_service.is_local? && !data_service.active
raise "Data service #{data_service.name} is not online, and won't be set as active"
end
prev_data_service = @current_data_service
@current_data_service = data_service
# reset the previous data service's active flag if it is remote
# to ensure checks are performed the next time it is set
if !prev_data_service.nil? && !prev_data_service.is_local?
prev_data_service.active = false
end
end
#
# Retrieves metadata about the data services
#
def get_services_metadata()
services_metadata = []
@data_services.each_key {|key|
name = @data_services[key].name
active = !@current_data_service.nil? && name == @current_data_service.name
is_local = @data_services[key].is_local?
services_metadata << Metasploit::Framework::DataService::Metadata.new(key, name, active, is_local)
}
services_metadata
end
#
# Used to bridge the local db
#
def method_missing(method, *args, &block)
unless @current_data_service.nil?
@current_data_service.send(method, *args, &block)
end
end
def respond_to?(method_name, include_private=false)
unless @current_data_service.nil?
return @current_data_service.respond_to?(method_name, include_private)
end
false
end
def get_data_service
raise 'No registered data_service' unless @current_data_service
return @current_data_service
end
# Performs a set of data service operations declared within the block.
# This passes the @current_data_service as a parameter to the block.
# If there is no current data service registered or the data service
# is not active, the block is not executed and the method simply returns.
def data_service_operation(&block)
return unless block_given?
begin
data_service = self.get_data_service
rescue
return
end
block.call(data_service) if !data_service.nil? && self.active
end
def log_error(exception, ui_message)
elog(ui_message, error: exception)
# TODO: We should try to surface the original exception, instead of just a generic one.
# This should not display the full backtrace, only the message.
raise exception
end
# Adds a valid workspace value to the opts hash before sending on to the data layer.
#
# @param [Hash] opts The opts hash that will be passed to the data layer.
# @param [String] wspace A specific workspace name to add to the opts hash.
# @return [Hash] The opts hash with a valid :workspace value added.
def add_opts_workspace(opts, wspace = nil)
# If :id is present the user only wants a specific record, so workspace isn't needed
return if opts.key?(:id)
# If the user passed in a specific workspace then use that in opts
opts[:workspace] = wspace if wspace
# We only want to pass the workspace name, so grab it if it is currently an object.
if opts[:workspace] && opts[:workspace].is_a?(::Mdm::Workspace)
opts[:workspace] = opts[:workspace].name
end
# If we still don't have a :workspace value, just set it to the current workspace.
opts[:workspace] = workspace.name if opts[:workspace].nil?
opts
end
#######
private
#######
def setup(opts)
begin
db_manager = opts.delete(:db_manager)
if !db_manager.nil?
register_data_service(db_manager)
@usable = true
else
@error = 'disabled'
end
rescue => e
@error = e
raise "Unable to initialize data service: #{e.message}"
end
end
def validate(data_service)
raise "Invalid data_service: #{data_service.class}, not of type Metasploit::Framework::DataService" unless data_service.is_a? (Metasploit::Framework::DataService)
raise 'Cannot register null data service data_service' unless data_service
raise 'Data Service already exists' if data_service_exist?(data_service)
# Raising an error for local DB causes startup to fail if there is a DB configured but we are unable to connect
# TODO: The check here shouldn't be dependent on if the data_service is local or not. We shouldn't
# connect to any data service if it is not online/active. This can likely be fixed by making a true
# LocalDataService instead of using DBManager.
unless data_service.is_local?
raise 'Data Service does not appear to be responding' unless data_service.active
end
end
def data_service_exist?(data_service)
@data_services.each_value{|value|
if (value.name == data_service.name)
return true
end
}
return false
end
end
end
end
end