# -*- coding: binary -*-
require 'msf/core'

module Msf

  autoload :OptionContainer, 'msf/core/option_container'

###
#
# The module base class is responsible for providing the common interface
# that is used to interact with modules at the most basic levels, such as
# by inspecting a given module's attributes (name, description, version,
# authors, etc) and by managing the module's data store.
#
###
class Module
  autoload :Arch, 'msf/core/module/arch'
  autoload :Author, 'msf/core/module/author'
  autoload :AuxiliaryAction, 'msf/core/module/auxiliary_action'
  autoload :Compatibility, 'msf/core/module/compatibility'
  autoload :DataStore, 'msf/core/module/data_store'
  autoload :Deprecated, 'msf/core/module/deprecated'
  autoload :Failure, 'msf/core/module/failure'
  autoload :FullName, 'msf/core/module/full_name'
  autoload :HasActions, 'msf/core/module/has_actions'
  autoload :ModuleInfo, 'msf/core/module/module_info'
  autoload :ModuleStore, 'msf/core/module/module_store'
  autoload :Network, 'msf/core/module/network'
  autoload :Options, 'msf/core/module/options'
  autoload :Platform, 'msf/core/module/platform'
  autoload :PlatformList, 'msf/core/module/platform_list'
  autoload :Privileged, 'msf/core/module/privileged'
  autoload :Ranking, 'msf/core/module/ranking'
  autoload :Reference, 'msf/core/module/reference'
  autoload :Search, 'msf/core/module/search'
  autoload :SiteReference, 'msf/core/module/reference'
  autoload :Target, 'msf/core/module/target'
  autoload :Type, 'msf/core/module/type'
  autoload :UI, 'msf/core/module/ui'
  autoload :UUID, 'msf/core/module/uuid'

  include Msf::Module::Arch
  include Msf::Module::Author
  include Msf::Module::Compatibility
  include Msf::Module::DataStore
  include Msf::Module::FullName
  include Msf::Module::ModuleInfo
  include Msf::Module::ModuleStore
  include Msf::Module::Network
  include Msf::Module::Options
  include Msf::Module::Privileged
  include Msf::Module::Ranking
  include Msf::Module::Search
  include Msf::Module::Type
  include Msf::Module::UI
  include Msf::Module::UUID

  # The key where a comma-separated list of Ruby module names will live in the
  # datastore, consumed by #replicant to allow clean override of MSF module methods.
  REPLICANT_EXTENSION_DS_KEY = 'ReplicantExtensions'

  # Make include public so we can runtime extend
  public_class_method :include

  class << self
    include Framework::Offspring

    #
    # This attribute holds the non-duplicated copy of the module
    # implementation.  This attribute is used for reloading purposes so that
    # it can be re-duplicated.
    #
    attr_accessor :orig_cls

    #
    # The path from which the module was loaded.
    #
    attr_accessor :file_path
  end

  #
  # Returns the class reference to the framework
  #
  def framework
    self.class.framework
  end

  #
  # This method allows modules to tell the framework if they are usable
  # on the system that they are being loaded on in a generic fashion.
  # By default, all modules are indicated as being usable.  An example of
  # where this is useful is if the module depends on something external to
  # ruby, such as a binary.
  #
  def self.is_usable
    true
  end

  #
  # Creates an instance of an abstract module using the supplied information
  # hash.
  #
  def initialize(info = {})
    @module_info_copy = info.dup

    self.module_info = info
    generate_uuid

    set_defaults

    # Initialize module compatibility hashes
    init_compat

    # Fixup module fields as needed
    info_fixups

    # Transform some of the fields to arrays as necessary
    self.author = Msf::Author.transform(module_info['Author'])
    self.arch = Rex::Transformer.transform(module_info['Arch'], Array, [ String ], 'Arch')
    self.platform = PlatformList.transform(module_info['Platform'])
    self.references = Rex::Transformer.transform(module_info['References'], Array, [ SiteReference, Reference ], 'Ref')

    # Create and initialize the option container for this module
    self.options = Msf::OptionContainer.new
    self.options.add_options(info['Options'], self.class)
    self.options.add_advanced_options(info['AdvancedOptions'], self.class)
    self.options.add_evasion_options(info['EvasionOptions'], self.class)

    # Create and initialize the data store for this module
    self.datastore = ModuleDataStore.new(self)

    # Import default options into the datastore
    import_defaults

    self.privileged = module_info['Privileged'] || false
    self.license = module_info['License'] || MSF_LICENSE

    # Allow all modules to track their current workspace
    register_advanced_options(
      [
        OptString.new('WORKSPACE', [ false, "Specify the workspace for this module" ]),
        OptBool.new('VERBOSE',     [ false, 'Enable detailed status messages', false ])
      ], Msf::Module)

  end

  #
  # Creates a fresh copy of an instantiated module
  #
  def replicant
    obj = self.class.new
    self.instance_variables.each { |k|
      v = instance_variable_get(k)
      v = v.dup rescue v
      obj.instance_variable_set(k, v)
    }

    obj.datastore    = self.datastore.copy
    obj.user_input   = self.user_input
    obj.user_output  = self.user_output
    obj.module_store = self.module_store.clone

    obj.perform_extensions
    obj
  end

  # Extends self with the constant list in the datastore
  # @return [void]
  def perform_extensions
    if datastore[REPLICANT_EXTENSION_DS_KEY].present?
      if datastore[REPLICANT_EXTENSION_DS_KEY].respond_to?(:each)
        datastore[REPLICANT_EXTENSION_DS_KEY].each do |const|
          self.extend(const)
        end
      else
        fail "Invalid settings in datastore at key #{REPLICANT_EXTENSION_DS_KEY}"
      end
    end
  end

  # @param[Constant] One or more Ruby constants
  # @return [void]
  def register_extensions(*rb_modules)
    datastore[REPLICANT_EXTENSION_DS_KEY] = [] unless datastore[REPLICANT_EXTENSION_DS_KEY].present?
    rb_modules.each do |rb_mod|
      datastore[REPLICANT_EXTENSION_DS_KEY] << rb_mod unless datastore[REPLICANT_EXTENSION_DS_KEY].include? rb_mod
    end
  end

  #
  # Returns the unduplicated class associated with this module.
  #
  def orig_cls
    self.class.orig_cls
  end

  #
  # The path to the file in which the module can be loaded from.
  #
  def file_path
    self.class.file_path
  end

  #
  # Checks to see if the target is vulnerable, returning unsupported if it's
  # not supported.
  #
  # This method is designed to be overriden by exploit modules.
  #
  def check
    Msf::Exploit::CheckCode::Unsupported
  end

  #
  # Returns the current workspace
  #
  def workspace
    self.datastore['WORKSPACE'] ||
      (framework.db and framework.db.active and framework.db.workspace and framework.db.workspace.name)
  end

  #
  # Returns the username that instantiated this module, this tries a handful of methods
  # to determine what actual user ran this module.
  #
  def owner
    # Generic method to configure a module owner
    username = self.datastore['MODULE_OWNER'].to_s.strip

    # Specific method used by the commercial products
    if username.empty?
      username = self.datastore['PROUSER'].to_s.strip
    end

    # Fallback when neither prior method is available, common for msfconsole
    if username.empty?
      username = (ENV['LOGNAME'] || ENV['USERNAME'] || ENV['USER'] || "unknown").to_s.strip
    end

    username
  end

  #
  # Scans the parent module reference to populate additional information. This
  # is used to inherit common settings (owner, workspace, parent uuid, etc).
  #
  def register_parent(ref)
    self.datastore['WORKSPACE']    = (ref.datastore['WORKSPACE'] ? ref.datastore['WORKSPACE'].dup : nil)
    self.datastore['PROUSER']      = (ref.datastore['PROUSER']   ? ref.datastore['PROUSER'].dup   : nil)
    self.datastore['MODULE_OWNER'] = ref.owner.dup
    self.datastore['ParentUUID']   = ref.uuid.dup
  end

  #
  # Return a comma separated list of supported platforms, if any.
  #
  def platform_to_s
    platform.all? ? "All" : platform.names.join(", ")
  end

  #
  # Checks to see if this module is compatible with the supplied platform
  #
  def platform?(what)
    (platform & what).empty? == false
  end

  #
  # Returns true if this module is being debugged.
  #
  def debugging?
    datastore['DEBUG']
  end

  #
  # Raises a RuntimeError failure message. This is meant to be used for all non-exploits,
  # and allows specific classes to override.
  #
  # @param reason [String] A reason about the failure.
  # @param msg [String] (Optional) A message about the failure.
  # @raise [RuntimeError]
  # @return [void]
  # @note If you are writing an exploit, you don't use this API. Instead, please refer to the
  #       API documentation from lib/msf/core/exploit.rb.
  # @see Msf::Exploit#fail_with
  # @example
  #   fail_with('No Access', 'Unable to login')
  #
  def fail_with(reason, msg=nil)
    raise RuntimeError, "#{reason.to_s}: #{msg}"
  end


  ##
  #
  # Just some handy quick checks
  #
  ##

  #
  # Returns false since this is the real module
  #
  def self.cached?
    false
  end

  #
  # The array of zero or more platforms.
  #
  attr_reader   :platform

  #
  # The reference count for the module.
  #
  attr_reader   :references

  #
  # The license under which this module is provided.
  #
  attr_reader   :license

  #
  # The job identifier that this module is running as, if any.
  #
  attr_accessor :job_id

  #
  # The last exception to occur using this module
  #
  attr_accessor :error

  # An opaque bag of data to attach to a module. This is useful for attaching
  # some piece of identifying info on to a module before calling
  # {Msf::Simple::Exploit#exploit_simple} or
  # {Msf::Simple::Auxiliary#run_simple} for correlating where modules came
  # from.
  #
  attr_accessor :user_data

  protected

  #
  # Sets the modules unsupplied info fields to their default values.
  #
  def set_defaults
    self.module_info = {
      'Name'        => 'No module name',
      'Description' => 'No module description',
      'Version'     => '0',
      'Author'      => nil,
      'Arch'        => nil, # No architectures by default.
      'Platform'    => [],  # No platforms by default.
      'Ref'         => nil,
      'Privileged'  => false,
      'License'     => MSF_LICENSE,
    }.update(self.module_info)
    self.module_store = {}
  end

  #
  # Checks to see if a derived instance of a given module implements a method
  # beyond the one that is provided by a base class.  This is a pretty lame
  # way of doing it, but I couldn't find a better one, so meh.
  #
  def derived_implementor?(parent, method_name)
    (self.method(method_name).to_s.match(/#{parent}[^:]/)) ? false : true
  end

  attr_writer   :platform, :references # :nodoc:
  attr_writer   :privileged # :nodoc:
  attr_writer   :license # :nodoc:

end
end
