# -*- coding: binary -*-
module Rex
module Ui

###
#
# This class implements the stubs that are needed to provide an interactive
# user interface that is backed against something arbitrary.
#
###
module Interactive

	#
	# Interactive sessions by default may interact with the local user input
	# and output.
	#
	include Rex::Ui::Subscriber

	#
	# Starts interacting with the session at the most raw level, simply
	# forwarding input from user_input to rstream and forwarding input from
	# rstream to user_output.
	#
	def interact(user_input, user_output)

		# Detach from any existing console
		if(self.interacting)
			detach()
		end

		init_ui(user_input, user_output)

		self.interacting = true
		self.completed = false

		eof = false

		# Start the readline stdin monitor
		# XXX disabled
		# user_input.readline_start() if user_input.supports_readline

		# Handle suspend notifications
		handle_suspend

		# As long as we're interacting...
		while (self.interacting == true)

			begin
				_interact

			rescue Interrupt
				# If we get an interrupt exception, ask the user if they want to
				# abort the interaction.  If they do, then we return out of
				# the interact function and call it a day.
				eof = true if (_interrupt)

			rescue EOFError, Errno::ECONNRESET, IOError
				# If we reach EOF or the connection is reset...
				eof = true

			end

			break if eof
		end

		begin

			# Restore the suspend handler
			restore_suspend

			# If we've hit eof, call the interact complete handler
			_interact_complete if (eof == true)

			# Shutdown the readline thread
 			# XXX disabled
			# user_input.readline_stop() if user_input.supports_readline

			# Detach from the input/output handles
			reset_ui()

		ensure
			# Mark this as completed
			self.completed = true
		end

		# Return whether or not EOF was reached
		return eof
	end

	#
	# Stops the current interaction
	#
	def detach
		if (self.interacting)
			self.interacting = false
			while(not self.completed)
				::IO.select(nil, nil, nil, 0.25)
			end
		end
	end

	#
	# Whether or not the session is currently being interacted with
	#
	attr_accessor   :interacting

	#
	# Whether or not the session has completed interaction
	#
	attr_accessor	:completed

	attr_accessor :on_print_proc
	attr_accessor :on_command_proc

protected

	#
	# The original suspend proc.
	#
	attr_accessor :orig_suspend

	#
	# Stub method that is meant to handler interaction
	#
	def _interact
	end

	#
	# Called when an interrupt is sent.
	#
	def _interrupt
		true
	end

	#
	# Called when a suspend is sent.
	#
	def _suspend
		false
	end

	#
	# Called when interaction has completed and one of the sides has closed.
	#
	def _interact_complete
		true
	end

	#
	# Read from remote and write to local.
	#
	def _stream_read_remote_write_local(stream)
		data = stream.get

		self.on_print_proc.call(data) if self.on_print_proc
		user_output.print(data)
	end

	#
	# Read from local and write to remote.
	#
	def _stream_read_local_write_remote(stream)
		data = user_input.gets

		self.on_command_proc.call(data) if self.on_command_proc
		stream.put(data)
	end

	#
	# The local file descriptor handle.
	#
	def _local_fd
		user_input.fd
	end

	#
	# The remote file descriptor handle.
	#
	def _remote_fd(stream)
		stream.fd
	end

	#
	# Interacts with two streaming connections, reading data from one and
	# writing it to the other.  Both are expected to implement Rex::IO::Stream.
	#
	def interact_stream(stream)
		while self.interacting

			# Select input and rstream
			sd = Rex::ThreadSafe.select([ _local_fd, _remote_fd(stream) ], nil, nil, 0.25)

			# Cycle through the items that have data
			# From the stream?  Write to user_output.
			sd[0].each { |s|
				if (s == _remote_fd(stream))
					_stream_read_remote_write_local(stream)
				# From user_input?  Write to stream.
				elsif (s == _local_fd)
					_stream_read_local_write_remote(stream)
				end
			} if (sd)

			Thread.pass
		end
	end


	#
	# Interacts between a local stream and a remote ring buffer. This has to use
	# a secondary thread to prevent the select on the local stream from blocking
	#
	def interact_ring(ring)
		begin

		rdr = Rex::ThreadFactory.spawn("RingMonitor", false) do
			seq = nil
			while self.interacting

				# Look for any pending data from the remote ring
				nseq,data = ring.read_data(seq)

				# Update the sequence number if necessary
				seq = nseq || seq

				# Write output to the local stream if successful
				user_output.print(data) if data

				# Wait for new data to arrive on this session
				ring.wait(seq)
			end
		end

		while self.interacting

			# Look for any pending input from the local stream
			sd = Rex::ThreadSafe.select([ _local_fd ], nil, [_local_fd], 5.0)

			# Write input to the ring's input mechanism
			if sd
				data = user_input.gets
				ring.put(data)
			end
		end

		ensure
			rdr.kill
		end
	end


	#
	# Installs a signal handler to monitor suspend signal notifications.
	#
	def handle_suspend
		if (orig_suspend == nil)
			begin
				self.orig_suspend = Signal.trap("TSTP") {
					_suspend
				}
			rescue
			end
		end
	end

	#
	# Restores the previously installed signal handler for suspend
	# notifications.
	#
	def restore_suspend
		begin
			if (orig_suspend)
				Signal.trap("TSTP", orig_suspend)
			else
				Signal.trap("TSTP", "DEFAULT")
			end
			self.orig_suspend = nil
		rescue
		end
	end

	#
	# Prompt the user for input if possible.
	# XXX: This is not thread-safe on Windows
	#
	def prompt(query)
		if (user_output and user_input)
			user_output.print("\n" + query)
			user_input.sysread(2)
		end
	end

	#
	# Check the return value of a yes/no prompt
	#
	def prompt_yesno(query)
		(prompt(query + " [y/N]  ") =~ /^y/i) ? true : false
	end

end

end
end

