Files
metasploit-gs/lib/rex/ui/text/shell.rb
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

527 lines
13 KiB
Ruby
Raw Normal View History

# -*- coding: binary -*-
2019-10-22 20:02:32 -04:00
require 'rex/text/color'
module Rex
module Ui
module Text
###
#
2009-11-10 15:33:14 +00:00
# The shell class provides a command-prompt style interface in a
# generic fashion.
#
###
module Shell
2019-10-22 20:02:32 -04:00
include Rex::Text::Color
###
#
# This module is meant to be mixed into an input medium class instance as a
# means of extending it to display a prompt before each call to gets.
#
###
module InputShell
attr_accessor :prompt, :output
2013-08-30 16:28:33 -05:00
2018-02-07 15:53:11 -05:00
def pgets
2013-08-30 16:28:33 -05:00
output.print(prompt)
output.flush
2013-08-30 16:28:33 -05:00
output.prompting
buf = gets
output.prompting(false)
2013-08-30 16:28:33 -05:00
buf
end
end
2013-08-30 16:28:33 -05:00
2005-11-15 05:22:13 +00:00
#
# Initializes a shell that has a prompt and can be interacted with.
#
2021-05-21 16:24:05 -04:00
def initialize(prompt, prompt_char = '>', histfile = nil, framework = nil, name = nil)
# Set the stop flag to false
self.stop_flag = false
self.disable_output = false
2017-10-17 16:22:47 -05:00
self.stop_count = 0
2021-05-04 22:59:42 +05:30
self.name = name
2013-08-30 16:28:33 -05:00
# Initialize the prompt
self.cont_prompt = ' > '
self.cont_flag = false
2018-09-13 15:51:07 -05:00
self.prompt = prompt
self.prompt_char = prompt_char
2013-08-30 16:28:33 -05:00
2009-11-26 07:13:21 +00:00
self.histfile = histfile
self.hist_last_saved = 0
2013-08-30 16:28:33 -05:00
# Static prompt variables
2022-02-08 17:40:03 -05:00
self.local_hostname = ENV['HOSTNAME'] || try_exec('hostname')&.split('.')&.first&.rstrip || ENV['COMPUTERNAME']
self.local_username = ENV['USER'] || try_exec('whoami')&.rstrip || ENV['USERNAME']
2011-07-21 06:14:25 +00:00
self.framework = framework
2005-07-18 04:07:56 +00:00
end
2013-08-30 16:28:33 -05:00
def init_tab_complete
if (self.input and self.input.supports_readline)
# Unless cont_flag because there's no tab complete for continuation lines
self.input = Input::Readline.new(lambda { |str| tab_complete(str) unless cont_flag })
self.input.output = self.output
end
end
2013-08-30 16:28:33 -05:00
2005-07-18 04:07:56 +00:00
#
# Initializes the user interface input/output classes.
#
def init_ui(in_input = nil, in_output = nil)
# Initialize the input and output methods
self.input = in_input
self.output = in_output
2013-08-30 16:28:33 -05:00
2005-07-18 04:07:56 +00:00
if (self.input)
# Extend the input medium as an input shell if the input medium
# isn't intrinsicly a shell.
if (self.input.intrinsic_shell? == false)
self.input.extend(InputShell)
end
2013-08-30 16:28:33 -05:00
2005-07-18 04:07:56 +00:00
self.input.output = self.output
end
end
2013-08-30 16:28:33 -05:00
2005-07-18 04:07:56 +00:00
#
2005-11-15 05:22:13 +00:00
# Resets the user interface handles.
2005-07-18 04:07:56 +00:00
#
def reset_ui
init_ui
end
2013-08-30 16:28:33 -05:00
2005-10-02 03:21:26 +00:00
#
# Sets the log source that should be used for logging input and output.
#
def set_log_source(log_source)
self.log_source = log_source
end
2013-08-30 16:28:33 -05:00
2005-10-02 03:21:26 +00:00
#
# Unsets the log source so that logging becomes disabled.
#
def unset_log_source
set_log_source(nil)
end
2013-08-30 16:28:33 -05:00
#
2005-11-15 05:22:13 +00:00
# Performs tab completion on the supplied string.
#
def tab_complete(str)
return tab_complete_proc(str) if (tab_complete_proc)
end
2013-08-30 16:28:33 -05:00
#
2005-11-15 05:22:13 +00:00
# Run the command processing loop.
#
2005-07-18 04:07:56 +00:00
def run(&block)
begin
require 'pry'
# pry history will not be loaded by default when pry is used as a breakpoint like `binding.pry`
Pry.config.history_load = false
rescue LoadError
# Pry is a development dependency, if not available suppressing history_load can be safely ignored.
end
2013-08-30 16:28:33 -05:00
with_history_manager_context do
begin
while true
# If the stop flag was set or we've hit EOF, break out
break if self.stop_flag || self.stop_count > 1
2013-08-30 16:28:33 -05:00
init_tab_complete
update_prompt
2013-08-30 16:28:33 -05:00
line = get_input_line
2013-08-30 16:28:33 -05:00
# If you have sessions active, this will give you a shot to exit
# gracefully. If you really are ambitious, 2 eofs will kick this out
if input.eof? || line == nil
self.stop_count += 1
next if self.stop_count > 1
2023-02-03 13:51:18 -05:00
if block
block.call('quit')
elsif respond_to?(:run_single)
# PseudoShell does not provide run_single
run_single('quit')
end
2018-02-20 03:27:55 -06:00
# If a block was passed in, pass the line to it. If it returns true,
# break out of the shell loop.
elsif block
break if block.call(line)
2018-02-20 04:40:00 -06:00
# Otherwise, call what should be an overridden instance method to
# process the line.
else
run_single(line)
self.stop_count = 0
end
end
# Prevent accidental console quits
rescue ::Interrupt
output.print("Interrupt: use the 'exit' command to quit\n")
retry
2008-11-18 19:41:13 +00:00
end
end
end
2013-08-30 16:28:33 -05:00
#
2005-11-15 05:22:13 +00:00
# Stop processing user input.
#
def stop
self.stop_flag = true
end
2013-08-30 16:28:33 -05:00
2005-07-18 05:13:21 +00:00
#
2005-11-15 05:22:13 +00:00
# Checks to see if the shell has stopped.
2005-07-18 05:13:21 +00:00
#
def stopped?
self.stop_flag
end
2013-08-30 16:28:33 -05:00
#
2005-11-15 05:22:13 +00:00
# Change the input prompt.
#
2011-07-21 06:14:25 +00:00
# prompt - the actual prompt
# new_prompt_char the char to append to the prompt
2018-08-17 14:32:48 -05:00
def update_prompt(new_prompt = self.prompt, new_prompt_char = self.prompt_char)
if (self.input)
2019-10-22 20:02:32 -04:00
p = substitute_colors(new_prompt + ' ' + new_prompt_char + ' ', true)
2013-08-30 16:28:33 -05:00
# Save the prompt before any substitutions
self.prompt = new_prompt
2018-08-17 14:32:48 -05:00
self.prompt_char = new_prompt_char
2013-08-30 16:28:33 -05:00
# Set the actual prompt to the saved prompt with any substitutions
# or updates from our output driver, be they color or whatever
2018-08-17 14:32:48 -05:00
self.input.prompt = self.output.update_prompt(format_prompt(p))
end
end
2013-08-30 16:28:33 -05:00
#
# Output shortcuts
#
2013-08-30 16:28:33 -05:00
2005-11-15 05:22:13 +00:00
#
# Prints an error message to the output handle.
#
2007-02-11 19:40:33 +00:00
def print_error(msg='')
return if (output.nil?)
2016-12-14 21:10:10 -08:00
return if (msg.nil?)
2013-08-30 16:28:33 -05:00
2011-02-15 05:45:01 +00:00
self.on_print_proc.call(msg) if self.on_print_proc
# Errors are not subject to disabled output
2005-10-02 03:21:26 +00:00
log_output(output.print_error(msg))
end
2013-08-30 16:28:33 -05:00
2016-11-15 19:20:42 -06:00
alias_method :print_bad, :print_error
2005-11-15 05:22:13 +00:00
#
# Prints a status message to the output handle.
#
2007-02-11 19:40:33 +00:00
def print_status(msg='')
return if (disable_output == true)
2013-08-30 16:28:33 -05:00
2011-02-15 05:45:01 +00:00
self.on_print_proc.call(msg) if self.on_print_proc
2005-10-02 03:21:26 +00:00
log_output(output.print_status(msg))
end
2013-08-30 16:28:33 -05:00
#
# Prints a good message to the output handle.
#
def print_good(msg='')
return if (disable_output == true)
2013-08-30 16:28:33 -05:00
2011-02-15 05:45:01 +00:00
self.on_print_proc.call(msg) if self.on_print_proc
log_output(output.print_good(msg))
end
2013-08-30 16:28:33 -05:00
2005-11-15 05:22:13 +00:00
#
# Prints a line of text to the output handle.
#
2007-02-11 19:40:33 +00:00
def print_line(msg='')
return if (disable_output == true)
2013-08-30 16:28:33 -05:00
2011-02-15 05:45:01 +00:00
self.on_print_proc.call(msg) if self.on_print_proc
2005-10-02 03:21:26 +00:00
log_output(output.print_line(msg))
end
2013-08-30 16:28:33 -05:00
2012-10-12 21:48:15 -05:00
#
# Prints a warning message to the output handle.
#
def print_warning(msg='')
return if (disable_output == true)
2013-08-30 16:28:33 -05:00
2012-10-12 21:48:15 -05:00
self.on_print_proc.call(msg) if self.on_print_proc
log_output(output.print_warning(msg))
end
2013-08-30 16:28:33 -05:00
2005-11-15 05:22:13 +00:00
#
# Prints a raw message to the output handle.
#
2007-02-11 19:40:33 +00:00
def print(msg='')
return if (disable_output == true)
2011-02-15 05:45:01 +00:00
self.on_print_proc.call(msg) if self.on_print_proc
2005-10-02 03:21:26 +00:00
log_output(output.print(msg))
end
2013-08-30 16:28:33 -05:00
2005-11-15 05:22:13 +00:00
#
# Whether or not output has been disabled.
#
attr_accessor :disable_output
2005-11-15 05:22:13 +00:00
#
# The input handle to read user input from.
#
attr_reader :input
#
# The output handle to write output to.
#
attr_reader :output
2013-08-30 16:28:33 -05:00
attr_reader :prompt, :prompt_char
2011-02-15 05:45:01 +00:00
attr_accessor :on_command_proc
attr_accessor :on_print_proc
2011-07-21 15:35:28 +00:00
attr_accessor :framework
attr_accessor :history_manager
2020-05-11 10:22:20 +01:00
attr_accessor :hist_last_saved # the number of history lines when last saved/loaded
2011-02-15 05:45:01 +00:00
2020-05-11 10:22:20 +01:00
protected
# Executes the yielded block under the context of a new HistoryManager context. The shell's history will be flushed
# to disk when no longer interacting with the shell. If no history manager is available, the history will not be persisted.
def with_history_manager_context
history_manager = self.history_manager || framework&.history_manager
return yield unless history_manager
begin
history_manager.with_context(history_file: histfile, name: name) do
self.hist_last_saved = Readline::HISTORY.length
yield
end
ensure
history_manager.flush
self.hist_last_saved = Readline::HISTORY.length
end
end
2019-10-22 20:02:32 -04:00
def supports_color?
true
end
#
# Get a single line of input, following continuation directives as necessary.
#
def get_input_line
line = "\\\n"
prompt_needs_reset = false
self.cont_flag = false
while line =~ /(^|[^\\])\\\s*$/
# Strip \ and all the trailing whitespace
line.sub!(/\\\s*/, '')
if line.length > 0
# Using update_prompt will overwrite the primary prompt
input.prompt = output.update_prompt(self.cont_prompt)
self.cont_flag = true
prompt_needs_reset = true
end
output.input = input
str = input.pgets
if str
line << str
else
2018-02-20 04:40:00 -06:00
line = nil
end
2018-02-20 04:40:00 -06:00
output.input = nil
log_output(input.prompt)
end
self.cont_flag = false
if prompt_needs_reset
# The continuation prompt was used so reset the prompt
update_prompt
end
line
end
#
2005-11-15 05:22:13 +00:00
# Parse a line into an array of arguments.
#
def parse_line(line)
2005-10-02 03:21:26 +00:00
log_input(line)
2013-08-30 16:28:33 -05:00
line.gsub!(/(\r|\n)/, '')
2013-08-30 16:28:33 -05:00
begin
return args = Rex::Parser::Arguments.from_s(line)
rescue ::ArgumentError
print_error("Parse error: #{$!}")
end
2013-08-30 16:28:33 -05:00
return []
end
2013-08-30 16:28:33 -05:00
2005-10-02 03:21:26 +00:00
#
# Print the prompt, but do not log it.
#
def _print_prompt(prompt)
output.print(prompt)
end
2013-08-30 16:28:33 -05:00
2005-10-02 03:21:26 +00:00
#
# Writes the supplied input to the log source if one has been registered.
#
def log_input(buf)
rlog(buf, log_source) if (log_source)
2005-10-02 03:21:26 +00:00
end
2013-08-30 16:28:33 -05:00
2005-10-02 03:21:26 +00:00
#
# Writes the supplied output to the log source if one has been registered.
#
def log_output(buf)
rlog(buf, log_source) if (log_source)
2005-10-02 03:21:26 +00:00
end
2013-08-30 16:28:33 -05:00
#
# Prompt the user for input if possible. Special edition for use inside commands.
#
def prompt_yesno(query)
p = "#{query} [y/N]"
2018-08-17 14:32:48 -05:00
old_p = [self.prompt, self.prompt_char]
update_prompt p, ' '
/^y/i === get_input_line
ensure
2018-08-17 14:32:48 -05:00
update_prompt *old_p
end
2018-08-17 14:10:28 -05:00
#
# Handle prompt substitutions
#
def format_prompt(str)
2019-10-06 12:08:53 -04:00
return str unless framework
# find the active session
session = framework.sessions.values.find { |session| session.interacting }
default = 'unknown'
2019-10-06 12:08:53 -04:00
2019-10-22 20:02:32 -04:00
formatted = ''
skip_next = false
for prefix, spec in str.split('').each_cons(2) do
if skip_next
skip_next = false
next
end
2019-10-06 13:55:53 -04:00
2019-10-22 20:02:32 -04:00
unless prefix == '%'
formatted << prefix
skip_next = false
next
2019-10-09 19:37:50 -04:00
end
2019-10-22 20:02:32 -04:00
skip_next = true
if spec == 'T'
if framework.datastore['PromptTimeFormat']
strftime_format = framework.datastore['PromptTimeFormat']
else
strftime_format = ::Time::DATE_FORMATS[:db].to_s
end
formatted << ::Time.now.strftime(strftime_format).to_s
2019-10-22 20:02:32 -04:00
elsif spec == 'W' && framework.db.active
formatted << framework.db.workspace.name
elsif session
sysinfo = session.respond_to?(:sys) ? session.sys.config.sysinfo : nil
case spec
when 'A'
formatted << (sysinfo.nil? ? default : sysinfo['Architecture'])
when 'D'
formatted << (session.respond_to?(:fs) ? session.fs.dir.getwd(refresh: false) : default)
when 'd'
formatted << ::Dir.getwd
when 'H'
formatted << (sysinfo.nil? ? default : sysinfo['Computer'])
when 'h'
formatted << (self.local_hostname || default).chomp
when 'I'
formatted << session.tunnel_peer
when 'i'
formatted << session.tunnel_local
when 'M'
formatted << session.session_type
when 'S'
formatted << session.sid.to_s
when 'U'
formatted << (session.respond_to?(:sys) ? session.sys.config.getuid(refresh: false) : default)
when 'u'
formatted << (self.local_username || default).chomp
else
formatted << prefix
skip_next = false
end
else
case spec
when 'H'
formatted << (self.local_hostname || default).chomp
when 'J'
formatted << framework.jobs.length.to_s
when 'U'
formatted << (self.local_username || default).chomp
when 'S'
formatted << framework.sessions.length.to_s
when 'L'
formatted << Rex::Socket.source_address
when 'D'
formatted << ::Dir.getwd
else
formatted << prefix
skip_next = false
end
end
2019-10-22 20:02:32 -04:00
end
2019-10-22 20:02:32 -04:00
if str.length > 0 && !skip_next
formatted << str[-1]
2018-08-17 14:10:28 -05:00
end
2019-10-22 20:02:32 -04:00
formatted
2018-08-17 14:10:28 -05:00
end
2005-11-15 05:22:13 +00:00
attr_writer :input, :output # :nodoc:
attr_writer :prompt, :prompt_char # :nodoc:
2018-08-17 14:32:48 -05:00
attr_accessor :stop_flag, :cont_prompt # :nodoc:
attr_accessor :tab_complete_proc # :nodoc:
attr_accessor :histfile # :nodoc:
2009-11-09 00:33:40 +00:00
attr_accessor :log_source, :stop_count # :nodoc:
attr_accessor :local_hostname, :local_username # :nodoc:
attr_reader :cont_flag # :nodoc:
2021-05-04 22:59:42 +05:30
attr_accessor :name
private
def try_exec(command)
begin
%x{ #{ command } }
rescue SystemCallError
nil
end
end
attr_writer :cont_flag # :nodoc:
end
end end end