#!/usr/bin/env ruby

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)
		super(client, cid, type, flags)
	end

	##
	#
	# Channel interaction
	#
	##

	#
	# Checks to see if the EOF flag has been set on the pool.
	#
	def eof
		request = Packet.create_request('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('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
			response = self.client.send_request(request)
		rescue
			return -1
		end

		return tell
	end

	#
	# Synonym for tell.
	#
	def pos
		return tell
	end

	#
	# This method returns the current file pointer position to the caller.
	#
	def tell
		request = Packet.create_request('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

