# -*- 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 # 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 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 # # 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