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
unstable
Matt Miller 2005-07-17 02:04:39 +00:00
parent 5ee93e6be6
commit 3d976dc22c
7 changed files with 200 additions and 17 deletions

View File

@ -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 <erne [at] powernav.com>, 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

View File

@ -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 ])

View File

@ -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

View File

@ -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

View File

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

View File

@ -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

21
lib/rex/ui/output/none.rb Normal file
View File

@ -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