# -*- coding: binary -*-

module Msf
module Simple

###
#
# A simplified exploit wrapper.
#
###
module Exploit

  include Module

  #
  # Wraps the exploitation process in a simple single method.  The options
  # hash can have the following values passed in it:
  #
  # Encoder
  #
  # 	The encoder module that should be used.
  #
  # Payload
  #
  # 	The payload module name that should be used.
  #
  # Target
  #
  # 	The selected target index.
  #
  # Nop
  #
  # 	The NOP generator that should be used in preference.
  #
  # OptionStr
  #
  # 	A string of comma separated option values that should be imported into
  # 	the datastore.
  #
  # Options
  #
  # 	A hash of values to be imported directly into the datastore.
  #
  # LocalInput
  #
  # 	The local input handle that data can be read in from.
  #
  # LocalOutput
  #
  # 	The local output through which data can be displayed.
  #
  # RunAsJob
  #
  # 	Whether or not the exploit should be run in the context of a background
  # 	job.
  #
  def self.exploit_simple(oexploit, opts, &block)
    exploit = oexploit.replicant
    # Trap and print errors here (makes them UI-independent)
    begin
      # Clone the module to prevent changes to the original instance

      Msf::Simple::Framework.simplify_module(exploit)
      yield(exploit) if block_given?

      # Import options from the OptionStr or Option hash.
      exploit._import_extra_options(opts)
      opts['Payload'] ||= exploit.datastore['Payload']

      unless opts['Quiet']
        exploit.init_ui(opts['LocalInput'] || exploit.user_input, opts['LocalOutput'] || exploit.user_output)
      else
        exploit.init_ui(nil, nil)
      end

      # Make sure parameters are valid.
      if (opts['Payload'] == nil)
        raise MissingPayloadError, 'A payload has not been selected.', caller
      end

      # Verify the options
      exploit.options.validate(exploit.datastore)

      # Start it up
      driver = ExploitDriver.new(exploit.framework)

      # Keep the handler of driver running if exploiting multiple targets.
      driver.keep_handler = true if opts['multi']

      # Initialize the driver instance
      driver.exploit    = exploit
      driver.payload    = exploit.framework.payloads.create(opts['Payload'])

      # Set the force wait for session flag if the caller requested force
      # blocking.  This is so that passive exploits can be blocked on from
      # things like the cli.
      driver.force_wait_for_session = true if (opts['ForceBlocking'] == true)

      # Was the payload valid?
      if (driver.payload == nil)
        raise MissingPayloadError,
          "You specified an invalid payload: #{opts['Payload']}", caller
      end

      # Use the supplied encoder, if any.  If one was not specified, then
      # nil will be assigned causing the exploit to default to picking the
      # best encoder.
      exploit.datastore['ENCODER'] = opts['Encoder'] if opts['Encoder']

      # Use the supplied NOP generator, if any.  If one was not specified, then
      # nil will be assigned causing the exploit to default to picking a
      # compatible NOP generator.
      exploit.datastore['NOP'] = opts['Nop'] if opts['Nop']

      # Force the payload to share the exploit's datastore
      driver.payload.share_datastore(driver.exploit.datastore)

      # Verify the payload options
      driver.payload.options.validate(driver.payload.datastore)

      # Set the target and then work some magic to derive index
      exploit.datastore['TARGET'] = opts['Target'] if opts['Target']
      target_idx = exploit.target_index

      if (target_idx == nil or target_idx < 0)
        raise MissingTargetError,
          "You must select a target.", caller
      end

      driver.target_idx = target_idx

      # Set the payload and exploit's subscriber values
      unless opts['Quiet']
        driver.payload.init_ui(opts['LocalInput'] || exploit.user_input, opts['LocalOutput'] || exploit.user_output)
      else
        driver.payload.init_ui(nil, nil)
      end

      if (opts['RunAsJob'])
        driver.use_job = true
      end

      # Let's rock this party
      driver.run

      # Save the job identifier this exploit is running as
      exploit.job_id  = driver.job_id

      # Propagate this back to the caller for console mgmt
      oexploit.job_id = exploit.job_id
    rescue ::Interrupt
      exploit.error = $!
      raise $!
    rescue ::Msf::OptionValidateError => e
      exploit.error = e
      ::Msf::Ui::Formatter::OptionValidateError.print_error(exploit, e)
      return false
    rescue ::Exception => e
      exploit.error = e
      exploit.print_error("Exploit failed: #{e}")
      elog("Exploit failed (#{exploit.refname})", error: e)
    end

    return driver.session if driver
    nil
  end

  #
  # Calls the class method.
  #
  def exploit_simple(opts, &block)
    Msf::Simple::Exploit.exploit_simple(self, opts, &block)
  end

  #
  # Initiates a check, setting up the exploit to be used.  The following
  # options can be specified:
  #
  # LocalInput
  #
  # 	The local input handle that data can be read in from.
  #
  # LocalOutput
  #
  # 	The local output through which data can be displayed.
  #
  def self.check_simple(mod, opts, job_listener: Msf::Simple::NoopJobListener.instance)
    Msf::Simple::Framework.simplify_module(mod)
    mod._import_extra_options(opts)

    if opts['LocalInput']
      mod.init_ui(opts['LocalInput'], opts['LocalOutput'])
    end

    unless mod.has_check?
      # Bail out early if the module doesn't have check
      raise ::NotImplementedError.new(Msf::Exploit::CheckCode::Unsupported.message)
    end

    # Validate the option container state so that options will
    # be normalized
    mod.validate

    run_uuid = Rex::Text.rand_text_alphanumeric(24)
    job_listener.waiting run_uuid
    ctx = [mod, run_uuid, job_listener]

    if opts['RunAsJob']
      mod.job_id = mod.framework.jobs.start_bg_job(
        "Exploit: #{mod.refname} check",
        ctx,
        Proc.new { |ctx_| self.job_check_proc(ctx_) },
        Proc.new { |ctx_| nil }
      )
      [run_uuid, mod.job_id]
    else
      self.job_check_proc(ctx)
    end
  end

  #
  # Calls the class method.
  #
  def check_simple(opts)
    Msf::Simple::Exploit.check_simple(self, opts)
  end

  protected

  def self.job_check_proc(ctx)
    mod = ctx[0]
    run_uuid = ctx[1]
    job_listener = ctx[2]
    begin
      job_listener.start run_uuid
      mod.setup
      result = mod.check
      job_listener.completed(run_uuid, result, mod)
    rescue => e
      job_listener.failed(run_uuid, e, mod)
      mod.handle_exception e
    ensure
      mod.cleanup
    end

    return result
  end
end

end
end
