# -*- coding: binary -*-

require 'rex/post/meterpreter/inbound_packet_handler'

module Rex
module Post
module Meterpreter

#
# The various types of channels
#
CHANNEL_CLASS_STREAM     = 1
CHANNEL_CLASS_DATAGRAM   = 2
CHANNEL_CLASS_POOL       = 3

#
# The various flags that can affect how the channel operates
#
#   CHANNEL_FLAG_SYNCHRONOUS
#      Specifies that I/O requests on the channel are blocking.
#
#   CHANNEL_FLAG_COMPRESS
#      Specifies that I/O requests on the channel have their data zlib compressed.
#
CHANNEL_FLAG_SYNCHRONOUS = (1 << 0)
CHANNEL_FLAG_COMPRESS    = (1 << 1)

#
# The core types of direct I/O requests
#
CHANNEL_DIO_READ         = 'read'
CHANNEL_DIO_WRITE        = 'write'
CHANNEL_DIO_CLOSE        = 'close'

###
#
# The channel class represents a logical data pipe that exists between the
# client and the server.  The purpose and behavior of the channel depends on
# which type it is.  The three basic types of channels are streams, datagrams,
# and pools.  Streams are basically equivalent to a TCP connection.
# Bidirectional, connection-oriented streams.  Datagrams are basically
# equivalent to a UDP session.  Bidirectional, connectionless.  Pools are
# basically equivalent to a uni-directional connection, like a file handle.
# Pools denote channels that only have requests flowing in one direction.
#
###
class Channel
  # Class modifications to support global channel message
  # dispatching without having to register a per-instance handler
  class << self
    include Rex::Post::Meterpreter::InboundPacketHandler

    # Class request handler for all channels that dispatches requests
    # to the appropriate class instance's DIO handler
    def request_handler(client, packet)
      cid = packet.get_tlv_value(TLV_TYPE_CHANNEL_ID)

      # No channel identifier, then drop it
      if cid.nil?
        return false
      end

      channel = client.find_channel(cid)

      # No valid channel context? The channel may not be registered yet
      if channel.nil?
        return false
      end


      dio = channel.dio_map(packet.method)

      # Supported DIO request? Dump it.
      if dio.nil?
        return true
      end


      # Call the channel's dio handler and return success or fail
      # based on what happens
      channel.dio_handler(dio, packet)
    end
  end

  ##
  #
  # Factory
  #
  ##

  #
  # Creates a logical channel between the client and the server
  # based on a given type.
  #
  def Channel.create(client, type = nil, klass = nil,
      flags = CHANNEL_FLAG_SYNCHRONOUS, addends = nil, **klass_kwargs)
    request = Packet.create_request(COMMAND_ID_CORE_CHANNEL_OPEN)

    # Set the type of channel that we're allocating
    if !type.nil?
      request.add_tlv(TLV_TYPE_CHANNEL_TYPE, type)
    end

    # If no factory class was provided, use the default native class
    if klass.nil?
      klass = self
    end

    request.add_tlv(TLV_TYPE_CHANNEL_CLASS, klass.cls)
    request.add_tlv(TLV_TYPE_FLAGS, flags)
    request.add_tlvs(addends)

    # Transmit the request and wait for the response
    cid = nil
    begin
      response = client.send_request(request)
      cid = response.get_tlv_value(TLV_TYPE_CHANNEL_ID)
      if cid.nil?
        raise Rex::Post::Meterpreter::RequestError
      end
    end

    # Create the channel instance
    klass.new(client, cid, type, flags, response, **klass_kwargs)
  end

  ##
  #
  # Constructor
  #
  ##

  #
  # Initializes the instance's attributes, such as client context,
  # class identifier, type, and flags.
  #
  def initialize(client, cid, type, flags, packet, **_)
    self.client = client
    self.cid    = cid
    self.type   = type
    self.flags  = flags
    @mutex  = Mutex.new

    # Add this instance to the list
    if (cid and client)
      client.add_channel(self)
    end

    # Ensure the remote object is closed when all references are removed
    ObjectSpace.define_finalizer(self, self.class.finalize(client, cid))
  end

  def self.finalize(client, cid)
    proc {
      unless cid.nil?
        deferred_close_proc = proc do
          begin
            self._close(client, cid)
          rescue => e
            elog("finalize method for Channel failed", error: e)
          end
        end

        # Schedule the finalizing logic out-of-band; as this logic might be called in the context of a Signal.trap, which can't synchronize mutexes
        client.framework.sessions.schedule(deferred_close_proc)
      end
    }
  end


  ##
  #
  # Channel interaction
  #
  ##

  #
  # Wrapper around the low-level channel read operation.
  #
  def read(length = nil, addends = nil)
    return _read(length, addends)
  end

  #
  # Reads data from the remote half of the channel.
  #
  def _read(length = nil, addends = nil)
    if self.cid.nil?
      raise IOError, "Channel has been closed.", caller
    end

    request = Packet.create_request(COMMAND_ID_CORE_CHANNEL_READ)

    if length.nil?
      # Default block size to a higher amount for passive dispatcher
      length = self.client.passive_service ? (1024*1024) : 65536
    end

    request.add_tlv(TLV_TYPE_CHANNEL_ID, self.cid)
    request.add_tlv(TLV_TYPE_LENGTH, length)
    request.add_tlvs(addends)

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

    # If the channel is in synchronous mode, the response should contain
    # data that was read from the remote side of the channel
    if (flag?(CHANNEL_FLAG_SYNCHRONOUS))
      data = response.get_tlv(TLV_TYPE_CHANNEL_DATA);

      if (data != nil)
        return data.value
      end
    else
      raise NotImplementedError, "Asynchronous channel mode is not implemented", caller
    end

    return nil
  end

  #
  # Wrapper around the low-level write.
  #
  def write(buf, length = nil, addends = nil)
    return _write(buf, length, addends)
  end

  #
  # Writes data to the remote half of the channel.
  #
  def _write(buf, length = nil, addends = nil)
    if self.cid.nil?
      raise IOError, "Channel has been closed.", caller
    end

    request = Packet.create_request(COMMAND_ID_CORE_CHANNEL_WRITE)

    # Truncation and celebration
    if ((length != nil) &&
        (buf.length >= length))
      buf = buf[0..length]
    else
      length = buf.length
    end

    # Populate the request
    request.add_tlv(TLV_TYPE_CHANNEL_ID, self.cid)

    cdata = request.add_tlv(TLV_TYPE_CHANNEL_DATA, buf)
    if( ( self.flags & CHANNEL_FLAG_COMPRESS ) == CHANNEL_FLAG_COMPRESS )
      cdata.compress = true
    end

    request.add_tlv(TLV_TYPE_LENGTH, length)
    request.add_tlvs(addends)

    response = self.client.send_request(request)
    written  = response.get_tlv(TLV_TYPE_LENGTH)

    written.nil? ? 0 : written.value
  end

  #
  # Wrapper around check for self.cid
  #
  def closed?
    self.cid.nil?
  end

  #
  # Wrapper around the low-level close.
  #
  def close(addends = nil)
    return _close(addends)
  end

  #
  # Close the channel for future writes.
  #
  def close_write
    return _close
  end

  #
  # Close the channel for future reads.
  #
  def close_read
    return _close
  end

  #
  # Closes the channel.
  #
  def self._close(client, cid, addends=nil)
    if cid.nil?
      raise IOError, "Channel has been closed.", caller
    end

    request = Packet.create_request(COMMAND_ID_CORE_CHANNEL_CLOSE)

    # Populate the request
    request.add_tlv(TLV_TYPE_CHANNEL_ID, cid)
    request.add_tlvs(addends)

    client.send_request(request, nil)

    # Disassociate this channel instance
    client.remove_channel(cid)

    return true
  end

  def _close(addends = nil)
    # let the finalizer do the work behind the scenes
    @mutex.synchronize {
      unless self.cid.nil?
        ObjectSpace.undefine_finalizer(self)
        self.class._close(self.client, self.cid, addends)
        self.cid = nil
      end
    }
  end
  #
  # Enables or disables interactive mode.
  #
  def interactive(tf = true, addends = nil)
    if self.cid.nil?
      raise IOError, "Channel has been closed.", caller
    end

    request = Packet.create_request(COMMAND_ID_CORE_CHANNEL_INTERACT)

    # Populate the request
    request.add_tlv(TLV_TYPE_CHANNEL_ID, self.cid)
    request.add_tlv(TLV_TYPE_BOOL, tf)
    request.add_tlvs(addends)

    self.client.send_request(request)

    return true
  end

  ##
  #
  # Direct I/O
  #
  ##

  #
  # Handles dispatching I/O requests based on the request packet.
  # The default implementation does nothing with direct I/O requests.
  #
  def dio_handler(dio, packet)
    if (dio == CHANNEL_DIO_READ)
      length = packet.get_tlv_value(TLV_TYPE_LENGTH)

      return dio_read_handler(packet, length)
    elsif (dio == CHANNEL_DIO_WRITE)
      data = packet.get_tlv_value(TLV_TYPE_CHANNEL_DATA)

      return dio_write_handler(packet, data)
    elsif (dio == CHANNEL_DIO_CLOSE)
      return dio_close_handler(packet)
    end
    return false;
  end

  #
  # Stub read handler.
  #
  def dio_read_handler(packet, length)
    return true
  end

  #
  # Stub write handler.
  #
  def dio_write_handler(packet, data)
    return true
  end

  #
  # Stub close handler.
  #
  def dio_close_handler(packet)
    temp_cid = nil
    @mutex.synchronize {
      temp_cid = self.cid
      self.cid = nil
    }
    client.remove_channel(temp_cid)

    # Trap IOErrors as parts of the channel may have already been closed
    begin
      self.cleanup
    rescue IOError
    end

    return true
  end

  #
  # Maps packet request methods to DIO request identifiers on a
  # per-instance basis as other instances may add custom dio
  # handlers.
  #
  def dio_map(command_id)
    if command_id == COMMAND_ID_CORE_CHANNEL_READ
      return CHANNEL_DIO_READ
    elsif command_id == COMMAND_ID_CORE_CHANNEL_WRITE
      return CHANNEL_DIO_WRITE
    elsif command_id == COMMAND_ID_CORE_CHANNEL_CLOSE
      return CHANNEL_DIO_CLOSE
    end

    return nil
  end

  ##
  #
  # Conditionals
  #
  ##

  #
  # Checks to see if a flag is set on the instance's flags attribute.
  #
  def flag?(flag)
    return ((self.flags & flag) == flag)
  end

  #
  # Returns whether or not the channel is operating synchronously.
  #
  def synchronous?
    return (self.flags & CHANNEL_FLAG_SYNCHRONOUS)
  end

  #
  # The unique channel identifier.
  #
  attr_reader   :cid
  #
  # The type of channel.
  #
  attr_reader   :type
  #
  # The class of channel (stream, datagram, pool).
  #
  attr_reader   :cls
  #
  # Any channel-specific flag, like synchronous IO.
  #
  attr_reader   :flags
  #
  # Any channel-specific parameters.
  #
  attr_accessor :params
  #
  # The associated meterpreter client instance
  #
  attr_accessor :client
protected


  attr_writer   :cid, :type, :cls, :flags # :nodoc:

  #
  # Cleans up any lingering resources
  #
  def cleanup
  end

end

end; end; end
