From 3d976dc22c595be96bc8c336a0e718c926624624 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Sun, 17 Jul 2005 02:04:39 +0000 Subject: [PATCH] minor improvements to session interaction, dumping sessions, interacting with sessions that are backgrounded git-svn-id: file:///home/svn/incoming/trunk@2772 4d416f70-5f16-0410-b530-b9f4589650da --- lib/msf/base/serializer/readable_text.rb | 34 +++++++++-- lib/msf/core/session/basic.rb | 25 +++++--- lib/msf/core/session/interactive.rb | 58 +++++++++++++++++++ lib/msf/core/session_manager.rb | 13 ++++- lib/msf/ui/console/command_dispatcher/core.rb | 54 +++++++++++++++++ lib/rex/ui/output.rb | 12 +++- lib/rex/ui/output/none.rb | 21 +++++++ 7 files changed, 200 insertions(+), 17 deletions(-) create mode 100644 lib/rex/ui/output/none.rb diff --git a/lib/msf/base/serializer/readable_text.rb b/lib/msf/base/serializer/readable_text.rb index 251c5f3184..0b53acceb9 100644 --- a/lib/msf/base/serializer/readable_text.rb +++ b/lib/msf/base/serializer/readable_text.rb @@ -13,6 +13,9 @@ module Serializer ### class ReadableText + DefaultColumnWrap = 60 + DefaultIndent = 4 + # # Returns a formatted string that contains information about # the supplied module instance. @@ -195,7 +198,7 @@ class ReadableText # Dumps the list of options associated with the # supplied module. # - def self.dump_options(mod, indent = 4) + def self.dump_options(mod, indent = DefaultIndent) tbl = Rex::Ui::Text::Table.new( 'Indent' => indent, 'Columns' => @@ -219,7 +222,7 @@ class ReadableText return tbl.to_s end - def self.dump_advanced_options(mod, indent = 4) + def self.dump_advanced_options(mod, indent = DefaultIndent) output = '' pad = ' ' * indent @@ -241,7 +244,7 @@ class ReadableText # # Dumps the contents of a datastore # - def self.dump_datastore(name, ds, indent = 4, col = 60) + def self.dump_datastore(name, ds, indent = DefaultIndent, col = DefaultColumnWrap) tbl = Rex::Ui::Text::Table.new( 'Indent' => indent, 'Header' => name, @@ -258,11 +261,34 @@ class ReadableText return ds.length > 0 ? tbl.to_s : "#{tbl.header_to_s}No entries in data store.\n" end + # + # Dumps the list of active sessions + # + def self.dump_sessions(framework, indent = DefaultIndent, col = DefaultColumnWrap) + tbl = Rex::Ui::Text::Table.new( + 'Indent' => indent, + 'Header' => "Active sessions", + 'Columns' => + [ + 'Id', + 'Description', + 'Tunnel' + ]) + + framework.sessions.each_sorted { |k| + session = framework.sessions[k] + + tbl << [ session.sid.to_s, session.desc, session.tunnel_to_s ] + } + + return framework.sessions.length > 0 ? tbl.to_s : "#{tbl.header_to_s}No active sessions.\n" + end + # # Jacked from Ernest Ellingson , modified # a bit to add indention # - def self.word_wrap(str, indent = 4, col = 60) + def self.word_wrap(str, indent = 4, col = DefaultColumnWrap) return Rex::Text.wordwrap(str, indent, col) end diff --git a/lib/msf/core/session/basic.rb b/lib/msf/core/session/basic.rb index d9fa38f6a9..8039cdc4ce 100644 --- a/lib/msf/core/session/basic.rb +++ b/lib/msf/core/session/basic.rb @@ -72,31 +72,38 @@ module Basic # rstream to loutput. # def interact + # Call the parent in case it has some work to do + super + eof = false + # Handle suspend notifications + handle_suspend + callcc { |ctx| - while true + # As long as we're interacting... + while (self.interacting == true) begin _interact # 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. rescue Interrupt - loutput.print("\nStop interacting with session #{name}? [y/N] ") - - r = linput.gets - - # Break out of the continuation - ctx.call if (r =~ /^y/i) + if (user_want_abort? == true) + eof = true + ctx.call + end rescue EOFError dlog("Session #{name} got EOF, closing.", 'core', LEV_1) - eof = true ctx.call end end } + # Restore the suspend handler + restore_suspend + # If we hit end-of-file, then that means we should finish off this # session and call it a day. framework.sessions.deregister(self) if (eof == true) @@ -122,7 +129,7 @@ protected # overriden by derived classes if they wish to do this another way. # def _interact - while true + while self.interacting # Select input and rstream sd = Rex::ThreadSafe.select([ linput.fd, rstream.fd ]) diff --git a/lib/msf/core/session/interactive.rb b/lib/msf/core/session/interactive.rb index c31ba92c58..55cf7dbdc7 100644 --- a/lib/msf/core/session/interactive.rb +++ b/lib/msf/core/session/interactive.rb @@ -24,6 +24,7 @@ module Interactive # Starts interacting with the session. # def interact + self.interacting = true end # @@ -34,6 +35,63 @@ module Interactive # The local output handle. Must inherit from Rex::Ui::Output. # attr_accessor :loutput + # + # Whether or not the session is currently being interacted with + # + attr_reader :interacting + +protected + + attr_writer :interacting + # + # The original suspend proc. + # + attr_accessor :orig_suspend + + # + # Checks to see if the user wants to abort + # + def user_want_abort? + prompt_yesno("Abort session #{name}? [y/N] ") + end + + # + # Installs a signal handler to monitor suspend signal notifications. + # + def handle_suspend + if (orig_suspend == nil) + self.orig_suspend = Signal.trap("TSTP") { + # Ask the user if they would like to background the session + if (prompt_yesno("Background session #{name}? [y/N] ") == true) + self.interacting = false + end + } + end + end + + # + # Restores the previously installed signal handler for suspend + # notifications. + # + def restore_suspend + if (orig_suspend) + Signal.trap("TSTP", orig_suspend) + + self.orig_suspend = nil + end + end + + def prompt(query) + loutput.print("\n" + query) + linput.gets + end + + # + # Check the return value of the prompt + # + def prompt_yesno(query) + (prompt(query) =~ /^y/i) ? true : false + end end diff --git a/lib/msf/core/session_manager.rb b/lib/msf/core/session_manager.rb index 55bef82811..090b73de62 100644 --- a/lib/msf/core/session_manager.rb +++ b/lib/msf/core/session_manager.rb @@ -25,6 +25,13 @@ class SessionManager < Hash self.sid_pool = 0 end + # + # Enumerates the sorted list of keys. + # + def each_sorted(&block) + self.keys.sort.each(&block) + end + # # Registers the supplied session object with the framework and returns # a unique session identifier to the caller. @@ -38,7 +45,7 @@ class SessionManager < Hash next_sid = (self.sid_pool += 1) # Insert the session into the session hash table - self[next_sid] = session + self[next_sid.to_i] = session # Initialize the session's sid and framework instance pointer session.sid = next_sid @@ -58,7 +65,7 @@ class SessionManager < Hash framework.events.on_session_close(session) # Remove it from the hash - self.delete(session.sid) + self.delete(session.sid.to_i) # Close it down session.cleanup @@ -68,7 +75,7 @@ class SessionManager < Hash # Returns the session associated with the supplied sid, if any # def get(sid) - return self[sid] + return self[sid.to_i] end protected diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 3407e5383e..2c4b98d30b 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -12,6 +12,11 @@ class Core include Msf::Ui::Console::CommandDispatcher + @@session_opts = Rex::Parser::Arguments.new( + "-i" => [ true, "Interact with the supplied session identifier." ], + "-h" => [ false, "Help banner." ], + "-l" => [ false, "List all active sessions." ]) + # Returns the list of commands supported by this command dispatcher def commands { @@ -24,6 +29,7 @@ class Core "quit" => "Exit the console", "save" => "Saves the active datastores", "search" => "Adds one or more module search paths", + "session" => "Dump session listings and display information about sessions", "set" => "Sets a variable to a value", "setg" => "Sets a global variable to a value", "show" => "Displays modules of a given type, or all modules", @@ -216,6 +222,54 @@ class Core recalculate_tab_complete end + # + # Provides an interface to the sessions currently active in the framework. + # + def cmd_session(*args) + if (args.length == 0) + args.unshift("-h") + end + + begin + @@session_opts.parse(args) { |opt, idx, val| + sid = nil + + case opt + # Interact with the supplied session identifier + when "-i" + if ((session = framework.sessions.get(val))) + if (session.interactive?) + print_status("Starting interaction with #{session.name}...\n") + + session.interact + else + print_error("Session #{val} is non-interactive.") + end + else + print_error("Invalid session identifier: #{val}") + end + + # Display the list of active sessions + when "-l" + print("\n" + + Serializer::ReadableText.dump_sessions(framework) + "\n") + + # Display help banner + when "-h" + print( + "Usage: session [options]\n\n" + + "Active session manipulation and interaction.\n" + + @@session_opts.usage()) + return false + end + } + rescue + log_error("Session manipulation failed: #{$!}") + end + + return true + end + # # Sets a name to a value in a context aware environment # diff --git a/lib/rex/ui/output.rb b/lib/rex/ui/output.rb index cc7f2f2247..87df35b5c0 100644 --- a/lib/rex/ui/output.rb +++ b/lib/rex/ui/output.rb @@ -14,6 +14,10 @@ module Ui ### class Output + # General output + require 'rex/ui/output/none' + + # Text-based output require 'rex/ui/text/output' # @@ -41,11 +45,17 @@ class Output end # - # Prints a message with no decoration + # Prints a message with no decoration. # def print(msg) end + # + # Flushes any buffered output. + # + def flush + end + end end diff --git a/lib/rex/ui/output/none.rb b/lib/rex/ui/output/none.rb new file mode 100644 index 0000000000..3f640d02f8 --- /dev/null +++ b/lib/rex/ui/output/none.rb @@ -0,0 +1,21 @@ +require 'rex/ui' + +module Rex +module Ui + +### +# +# None +# ---- +# +# This output medium implements all the required output routines but does not +# back them against any sort of device. This is basically meant to be put in +# place of something that expects to be able to deal with a functional output +# device when one does not actually exist. +# +### +class Output::None < Rex::Ui::Output +end + +end +end