# -*- coding: binary -*-

require 'rex/post/meterpreter/channel'

module Rex
module Post
module Meterpreter
module Channels

###
#
# This class acts as a base class for all channels that are classified
# as 'pools'.  This means that only one side of the channel, typically
# the client half, acts on the other half of the channel.  Examples
# of pools come in the form of files where the remote side never sends
# any unrequested data.
#
# Another key distinction of Pools is that they, in general, support
# the DIO mode 'seek' which allows for changing the position, or offset,
# into the channel.
#
###
class Pool < Rex::Post::Meterpreter::Channel

  class << self
    def cls
      return CHANNEL_CLASS_POOL
    end
  end

  ##
  #
  # Constructor
  #
  ##

  #
  # Passes the initialization information up to the base class
  #
  def initialize(client, cid, type, flags, packet, **_)
    super(client, cid, type, flags, packet)
  end

  ##
  #
  # Channel interaction
  #
  ##

  #
  # Checks to see if the EOF flag has been set on the pool.
  #
  def eof
    request = Packet.create_request(COMMAND_ID_CORE_CHANNEL_EOF)

    request.add_tlv(TLV_TYPE_CHANNEL_ID, self.cid)

    begin
      response = self.client.send_request(request)
    rescue
      return true
    end

    if (response.has_tlv?(TLV_TYPE_BOOL))
      return response.get_tlv_value(TLV_TYPE_BOOL)
    end

    return false
  end

  #
  # Reads data from the remote side of the pool and raises EOFError if the
  # pool has been reached EOF.
  #
  def read(length = nil)
    begin
      data = super(length)
    rescue
      data = nil
    end

    if (((data == nil) || (data.length == 0)) &&
        (self.eof))
      raise EOFError
    end

    return data
  end

  #
  # This method seeks to an offset within the remote side of the pool using
  # the standard seek whence clauses.
  #
  def seek(offset, whence = SEEK_SET)
    sane = 0

    # Just in case...
    case whence
      when ::IO::SEEK_SET
        sane = 0
      when ::IO::SEEK_CUR
        sane = 1
      when ::IO::SEEK_END
        sane = 2
      else
        raise RuntimeError, "Invalid seek whence #{whence}.", caller
    end

    request = Packet.create_request(COMMAND_ID_CORE_CHANNEL_SEEK)

    request.add_tlv(TLV_TYPE_CHANNEL_ID, self.cid)
    request.add_tlv(TLV_TYPE_SEEK_OFFSET, offset)
    request.add_tlv(TLV_TYPE_SEEK_WHENCE, sane)

    begin
      self.client.send_request(request)
      tell
    rescue
      return -1
    end
  end

  #
  # Synonym for tell.
  #
  def pos
    tell
  end

  #
  # This method returns the current file pointer position to the caller.
  #
  def tell
    request = Packet.create_request(COMMAND_ID_CORE_CHANNEL_TELL)
    pos     = -1

    request.add_tlv(TLV_TYPE_CHANNEL_ID, self.cid)

    begin
      response = self.client.send_request(request)
    rescue
      return pos
    end

    # Set the return value to the position that we're at
    if (response.has_tlv?(TLV_TYPE_SEEK_POS))
      pos = response.get_tlv_value(TLV_TYPE_SEEK_POS)
    end

    return pos
  end

protected
  attr_accessor :_eof # :nodoc:

end

end; end; end; end

