metasploit-framework/lib/rex/ui/interactive.rb

315 lines
6.3 KiB
Ruby
Raw Normal View History

# -*- 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
2013-08-30 21:28:33 +00:00
#
# Interactive sessions by default may interact with the local user input
# and output.
#
include Rex::Ui::Subscriber
2013-08-30 21:28:33 +00:00
#
# 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)
2013-08-30 21:28:33 +00:00
# Detach from any existing console
if(self.interacting)
detach()
end
2013-08-30 21:28:33 +00:00
init_ui(user_input, user_output)
2013-08-30 21:28:33 +00:00
self.interacting = true
self.completed = false
2013-08-30 21:28:33 +00:00
eof = false
2013-08-30 21:28:33 +00:00
# Start the readline stdin monitor
# XXX disabled
# user_input.readline_start() if user_input.supports_readline
2013-08-30 21:28:33 +00:00
# Handle suspend notifications
handle_suspend
2013-08-30 21:28:33 +00:00
# As long as we're interacting...
while (self.interacting == true)
2013-08-30 21:28:33 +00:00
begin
_interact
2013-08-30 21:28:33 +00:00
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)
2013-08-30 21:28:33 +00:00
rescue EOFError, Errno::ECONNRESET, IOError
# If we reach EOF or the connection is reset...
eof = true
2013-08-30 21:28:33 +00:00
end
2013-08-30 21:28:33 +00:00
break if eof
end
2013-08-30 21:28:33 +00:00
begin
2013-08-30 21:28:33 +00:00
# Restore the suspend handler
restore_suspend
2013-08-30 21:28:33 +00:00
# If we've hit eof, call the interact complete handler
_interact_complete if (eof == true)
2013-08-30 21:28:33 +00:00
# Shutdown the readline thread
# XXX disabled
2013-08-30 21:28:33 +00:00
# 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
# if another session was requested, store it
next_session = self.next_session
# clear the value from the object
self.next_session = nil
# return this session id
return next_session
2013-08-30 21:28:33 +00:00
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
#
# If another session needs interaction, this is where it goes
#
attr_accessor :next_session
2013-08-30 21:28:33 +00:00
#
# Whether or not the session has completed interaction
#
attr_accessor :completed
attr_accessor :on_print_proc
attr_accessor :on_command_proc
protected
2013-08-30 21:28:33 +00:00
#
# 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)
2013-08-30 21:28:33 +00:00
# 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?
2013-08-30 21:28:33 +00:00
begin
self.orig_suspend = Signal.trap("TSTP") do
Thread.new { _suspend }.join
end
2013-08-30 21:28:33 +00:00
rescue
end
end
end
#
# Restores the previously installed signal handler for suspend
# notifications.
#
def restore_suspend
begin
if orig_suspend
2013-08-30 21:28:33 +00:00
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