
module Msf::Module::External
  include Msf::Auxiliary::Report
  include Msf::Module::Auth

  def execute_module(path, method: :run, args: datastore, fail_on_exit: true)
    mod = Msf::Modules::External.new(path, framework: framework)
    success = mod.exec(method: method, args: args) do |m|
      begin
        case m.method
        when :message
          log_output(m)
        when :report
          process_report(m, mod)
        when :reply
          return m.params['return']
        end
      rescue Interrupt => e
        raise e
      rescue Exception => e
        elog('Unable to execute External Module', error: e)
        fail_with Msf::Module::Failure::Unknown, e.message
      end
    end

    fail_with Msf::Module::Failure::Unknown, "Module exited abnormally" if fail_on_exit && !success
  end

  def log_output(m)
    message = m.params['message']

    case m.params['level']
    when 'error'
      print_error message
    when 'warning'
      print_warning message
    when 'good'
      print_good message
    when 'info'
      print_status message
    when 'debug'
      vprint_status message
    else
      print_status message
    end
  end

  def process_report(m, mod)
    data = m.params['data']

    case m.params['type']
    when 'host'
      # Required
      host = {host: data['host']}

      # Optional
      host[:state] = data['state'] if data['state'] # TODO: validate -- one of the Msf::HostState constants (unknown, alive, dead)
      host[:os_name] = data['os_name'] if data['os_name']
      host[:os_flavor] = data['os_flavor'] if data['os_flavor']
      host[:os_sp] = data['os_sp'] if data['os_sp']
      host[:os_lang] = data['os_lang'] if data['os_lang']
      host[:arch] = data['arch'] if data['arch'] # TODO: validate -- one of the ARCH_* constants
      host[:mac] = data['mac'] if data['mac']
      host[:scope] = data['scope'] if data['scope']
      host[:virtual_host] = data['virtual_host'] if data['virtual_host']

      report_host(host)
    when 'service'
      # Required
      service = {host: data['host'], port: data['port'], proto: data['proto']}

      # Optional
      service[:name] = data['name'] || mod.meta['service_name'] if data['name'] || mod.meta['service_name']

      report_service(service)
    when 'vuln'
      # Required
      vuln = {host: data['host'], name: data['name']}

      # Optional
      vuln[:info] = data['info'] if data['info']
      vuln[:refs] = data['refs'] if data['refs']
      vuln[:port] = data['port'] if data['port']
      vuln[:proto] = data['port'] if data['port']

      # Metasploit magic
      vuln[:refs] = self.references

      report_vuln(vuln)
    when 'correct_password'
      # Required
      cred = {user: data['username'], private: data['password']}

      # Optional
      cred[:proof] = data['proof'] if data['proof']
      cred[:service_data] =
        {
          origin_type: :service,
          protocol: data['protocol'] || 'tcp',
          service_name: data['service_name'] || mod.meta['service_name'],
          address: data['host'] || datastore['rhost'] || rhost,
          port: data['port'] || datastore['rport'] || rport
        }

      cred[:private_type] = :password

      # Optional
      if data.has_key?('domain')
        cred[:service_data][:realm_value] = data['domain']
        cred[:service_data][:realm_key] = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
      end

      store_valid_credential(**cred)
    when 'wrong_password'
      # Required
      cred = {public: data['username'], private: data['password']}

      # Optional
      cred.merge!({
        address: data['host'] || datastore['rhost'] || rhost,
        port: data['port'] || datastore['rport'] || rport,
        protocol: data['protocol'] || 'tcp',
        status: Metasploit::Model::Login::Status::INCORRECT
      })

      invalidate_login(**cred)

    when 'credential_login'
      handle_credential_login(data, mod)
    else
      print_warning "Skipping unrecognized report type #{m.params['type']}"
    end
  end
end

#
# Handles login report that does not necessarily need to include a password
#
def handle_credential_login(data, mod)
  # Required
  service_data = {
      address: data['address'],
      port: data['port'],
      protocol: data['protocol'],
      service_name: data['service_name'],
      module_fullname: self.fullname,
      workspace_id: myworkspace_id
  }
  # Optional
  credential_data = {
      origin_type: :service,
      username: data['username']
  }.merge(service_data)

  if data.has_key?(:password)
    credential_data[:private_data] = data['password']
    credential_data[:private_type] = :password
  end

  if data.has_key?('domain')
    credential_data[:realm_value] = data['domain']
    credential_data[:realm_key] = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
  end

  login_data = {
      core: create_credential(credential_data),
      last_attempted_at: DateTime.now,
      status: Metasploit::Model::Login::Status::SUCCESSFUL,
  }.merge(service_data)
  create_credential_login(login_data)
end
