305 lines
6.1 KiB
Ruby
305 lines
6.1 KiB
Ruby
# -*- 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 && _remote_fd(stream)
|
|
|
|
# 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") do
|
|
Thread.new { _suspend }.join
|
|
end
|
|
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
|
|
|