# -*- coding: binary -*-

module Rex
module Post
module Meterpreter
module Extensions
module Extapi
module Adsi

###
#
# This meterpreter extension contains extended API functions for
# querying and managing desktop windows.
#
###
class Adsi

  def initialize(client)
    @client = client
  end

  #
  # Perform a generic domain query against ADSI.
  #
  # @param domain_name [String] The FQDN of the target domain.
  # @param filter [String] The filter to apply to the query in
  #   LDAP format.
  # @param max_results [Integer] The maximum number of results
  #   to return.
  # @param page_size [Integer] The size of the page of results
  #   to return.
  # @param fields [Array] Array of string fields to return for
  #   each result found
  #
  # @return [Hash] Array of field names with associated results.
  #
  def domain_query(domain_name, filter, max_results, page_size, fields)
    request = Packet.create_request(COMMAND_ID_EXTAPI_ADSI_DOMAIN_QUERY)

    request.add_tlv(TLV_TYPE_EXT_ADSI_DOMAIN, domain_name)
    request.add_tlv(TLV_TYPE_EXT_ADSI_FILTER, filter)
    request.add_tlv(TLV_TYPE_EXT_ADSI_MAXRESULTS, max_results)
    request.add_tlv(TLV_TYPE_EXT_ADSI_PAGESIZE, page_size)

    fields.each do |f|
      request.add_tlv(TLV_TYPE_EXT_ADSI_FIELD, f)
    end

    response = client.send_request(request)

    results = extract_results(response)

    return {
      :fields  => fields,
      :results => results
    }
  end

  attr_accessor :client

protected

  #
  # Retrieve the results of the query from the response
  #   packet that was returned from Meterpreter.
  #
  # @param response [Packet] Reference to the received
  #   packet that was returned from Meterpreter.
  #
  # @return [Array[Array[[Hash]]] Collection of results from
  #   the ADSI query.
  #
  def extract_results(response)
    results = []

    response.each(TLV_TYPE_EXT_ADSI_RESULT) do |r|
      results << extract_values(r)
    end

    results
  end

  #
  # Extract a single row of results from a TLV group.
  #
  # @param tlv_container [Packet] Reference to the TLV
  #   group to pull the values from.
  #
  # @return [Array[Hash]] Collection of values from
  #   the single ADSI query result row.
  #
  def extract_values(tlv_container)
    values = []
    tlv_container.get_tlvs(TLV_TYPE_ANY).each do |v|
      values << extract_value(v)
    end
    values
  end

  #
  # Convert a single ADSI result value into a usable
  #   value that also describes its type.
  #
  # @param v [TLV] The TLV item that contains the value.
  #
  # @return [Hash] The type/value pair from the TLV.
  #
  def extract_value(v)
    value = {
      :type => :unknown
    }

    case v.type
    when TLV_TYPE_EXT_ADSI_STRING
      value = {
        :type  => :string,
        :value => v.value
      }
    when TLV_TYPE_EXT_ADSI_NUMBER, TLV_TYPE_EXT_ADSI_BIGNUMBER
      value = {
        :type  => :number,
        :value => v.value
      }
    when TLV_TYPE_EXT_ADSI_BOOL
      value = {
        :type  => :bool,
        :value => v.value
      }
    when TLV_TYPE_EXT_ADSI_RAW
      value = {
        :type  => :raw,
        :value => v.value
      }
    when TLV_TYPE_EXT_ADSI_ARRAY
      value = {
        :type  => :array,
        :value => extract_values(v.value)
      }
    when TLV_TYPE_EXT_ADSI_PATH
      value = {
        :type     => :path,
        :volume   => v.get_tlv_value(TLV_TYPE_EXT_ADSI_PATH_VOL),
        :path     => v.get_tlv_value(TLV_TYPE_EXT_ADSI_PATH_PATH),
        :vol_type => v.get_tlv_value(TLV_TYPE_EXT_ADSI_PATH_TYPE)
      }
    when TLV_TYPE_EXT_ADSI_DN
      values = v.get_tlvs(TLV_TYPE_ALL)
      value = {
        :type   => :dn,
        :label  => values[0].value
      }

      if values[1].type == TLV_TYPE_EXT_ADSI_STRING
        value[:string] = value[1].value
      else
        value[:raw] = value[1].value
      end
    end

    value
  end
end

end; end; end; end; end; end

