Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/ui/text/shell.rb
Views: 11655
# -*- coding: binary -*-1require 'rex/text/color'23module Rex4module Ui5module Text67###8#9# The shell class provides a command-prompt style interface in a10# generic fashion.11#12###13module Shell1415include Rex::Text::Color1617###18#19# This module is meant to be mixed into an input medium class instance as a20# means of extending it to display a prompt before each call to gets.21#22###23module InputShell24attr_accessor :prompt, :output2526def pgets2728output.print(prompt)29output.flush3031output.prompting32buf = gets33output.prompting(false)3435buf36end37end3839#40# Initializes a shell that has a prompt and can be interacted with.41#42def initialize(prompt, prompt_char = '>', histfile = nil, framework = nil, name = nil)43# Set the stop flag to false44self.stop_flag = false45self.disable_output = false46self.stop_count = 047self.name = name4849# Initialize the prompt50self.cont_prompt = ' > '51self.cont_flag = false52self.prompt = prompt53self.prompt_char = prompt_char5455self.histfile = histfile56self.hist_last_saved = 05758# Static prompt variables59self.local_hostname = ENV['HOSTNAME'] || try_exec('hostname')&.split('.')&.first&.rstrip || ENV['COMPUTERNAME']60self.local_username = ENV['USER'] || try_exec('whoami')&.rstrip || ENV['USERNAME']6162self.framework = framework63end6465def init_tab_complete66if (self.input and self.input.supports_readline)67# Unless cont_flag because there's no tab complete for continuation lines68self.input = Input::Readline.new(lambda { |str| tab_complete(str) unless cont_flag })69self.input.output = self.output70end71end7273#74# Initializes the user interface input/output classes.75#76def init_ui(in_input = nil, in_output = nil)77# Initialize the input and output methods78self.input = in_input79self.output = in_output8081if (self.input)82# Extend the input medium as an input shell if the input medium83# isn't intrinsicly a shell.84if (self.input.intrinsic_shell? == false)85self.input.extend(InputShell)86end8788self.input.output = self.output89end90end9192#93# Resets the user interface handles.94#95def reset_ui96init_ui97end9899#100# Sets the log source that should be used for logging input and output.101#102def set_log_source(log_source)103self.log_source = log_source104end105106#107# Unsets the log source so that logging becomes disabled.108#109def unset_log_source110set_log_source(nil)111end112113#114# Performs tab completion on the supplied string.115#116def tab_complete(str)117return tab_complete_proc(str) if (tab_complete_proc)118end119120#121# Run the command processing loop.122#123def run(&block)124begin125require 'pry'126# pry history will not be loaded by default when pry is used as a breakpoint like `binding.pry`127Pry.config.history_load = false128rescue LoadError129# Pry is a development dependency, if not available suppressing history_load can be safely ignored.130end131132with_history_manager_context do133begin134while true135# If the stop flag was set or we've hit EOF, break out136break if self.stop_flag || self.stop_count > 1137138init_tab_complete139update_prompt140141line = get_input_line142143# If you have sessions active, this will give you a shot to exit144# gracefully. If you really are ambitious, 2 eofs will kick this out145if input.eof? || line == nil146self.stop_count += 1147next if self.stop_count > 1148149if block150block.call('quit')151elsif respond_to?(:run_single)152# PseudoShell does not provide run_single153run_single('quit')154end155156# If a block was passed in, pass the line to it. If it returns true,157# break out of the shell loop.158elsif block159break if block.call(line)160161# Otherwise, call what should be an overridden instance method to162# process the line.163else164run_single(line)165self.stop_count = 0166end167end168# Prevent accidental console quits169rescue ::Interrupt170output.print("Interrupt: use the 'exit' command to quit\n")171retry172end173end174end175176#177# Stop processing user input.178#179def stop180self.stop_flag = true181end182183#184# Checks to see if the shell has stopped.185#186def stopped?187self.stop_flag188end189190#191# Change the input prompt.192#193# prompt - the actual prompt194# new_prompt_char the char to append to the prompt195def update_prompt(new_prompt = self.prompt, new_prompt_char = self.prompt_char)196if (self.input)197p = substitute_colors(new_prompt + ' ' + new_prompt_char + ' ', true)198199# Save the prompt before any substitutions200self.prompt = new_prompt201self.prompt_char = new_prompt_char202203# Set the actual prompt to the saved prompt with any substitutions204# or updates from our output driver, be they color or whatever205self.input.prompt = self.output.update_prompt(format_prompt(p))206end207end208209#210# Output shortcuts211#212213#214# Prints an error message to the output handle.215#216def print_error(msg='')217return if (output.nil?)218return if (msg.nil?)219220self.on_print_proc.call(msg) if self.on_print_proc221# Errors are not subject to disabled output222log_output(output.print_error(msg))223end224225alias_method :print_bad, :print_error226227#228# Prints a status message to the output handle.229#230def print_status(msg='')231return if (disable_output == true)232233self.on_print_proc.call(msg) if self.on_print_proc234log_output(output.print_status(msg))235end236237#238# Prints a good message to the output handle.239#240def print_good(msg='')241return if (disable_output == true)242243self.on_print_proc.call(msg) if self.on_print_proc244log_output(output.print_good(msg))245end246247#248# Prints a line of text to the output handle.249#250def print_line(msg='')251return if (disable_output == true)252253self.on_print_proc.call(msg) if self.on_print_proc254log_output(output.print_line(msg))255end256257#258# Prints a warning message to the output handle.259#260def print_warning(msg='')261return if (disable_output == true)262263self.on_print_proc.call(msg) if self.on_print_proc264log_output(output.print_warning(msg))265end266267#268# Prints a raw message to the output handle.269#270def print(msg='')271return if (disable_output == true)272self.on_print_proc.call(msg) if self.on_print_proc273log_output(output.print(msg))274end275276#277# Whether or not output has been disabled.278#279attr_accessor :disable_output280#281# The input handle to read user input from.282#283attr_reader :input284#285# The output handle to write output to.286#287attr_reader :output288289attr_reader :prompt, :prompt_char290attr_accessor :on_command_proc291attr_accessor :on_print_proc292attr_accessor :framework293attr_accessor :history_manager294attr_accessor :hist_last_saved # the number of history lines when last saved/loaded295296protected297298# Executes the yielded block under the context of a new HistoryManager context. The shell's history will be flushed299# to disk when no longer interacting with the shell. If no history manager is available, the history will not be persisted.300def with_history_manager_context301history_manager = self.history_manager || framework&.history_manager302return yield unless history_manager303304begin305history_manager.with_context(history_file: histfile, name: name) do306self.hist_last_saved = Readline::HISTORY.length307308yield309end310ensure311history_manager.flush312self.hist_last_saved = Readline::HISTORY.length313end314end315316def supports_color?317true318end319320#321# Get a single line of input, following continuation directives as necessary.322#323def get_input_line324line = "\\\n"325prompt_needs_reset = false326327self.cont_flag = false328while line =~ /(^|[^\\])\\\s*$/329# Strip \ and all the trailing whitespace330line.sub!(/\\\s*/, '')331332if line.length > 0333# Using update_prompt will overwrite the primary prompt334input.prompt = output.update_prompt(self.cont_prompt)335self.cont_flag = true336prompt_needs_reset = true337end338339output.input = input340str = input.pgets341if str342line << str343else344line = nil345end346347output.input = nil348log_output(input.prompt)349end350self.cont_flag = false351352if prompt_needs_reset353# The continuation prompt was used so reset the prompt354update_prompt355end356357line358end359360#361# Parse a line into an array of arguments.362#363def parse_line(line)364log_input(line)365366line.gsub!(/(\r|\n)/, '')367368begin369return args = Rex::Parser::Arguments.from_s(line)370rescue ::ArgumentError371print_error("Parse error: #{$!}")372end373374return []375end376377#378# Print the prompt, but do not log it.379#380def _print_prompt(prompt)381output.print(prompt)382end383384#385# Writes the supplied input to the log source if one has been registered.386#387def log_input(buf)388rlog(buf, log_source) if (log_source)389end390391#392# Writes the supplied output to the log source if one has been registered.393#394def log_output(buf)395rlog(buf, log_source) if (log_source)396end397398#399# Prompt the user for input if possible. Special edition for use inside commands.400#401def prompt_yesno(query)402p = "#{query} [y/N]"403old_p = [self.prompt, self.prompt_char]404update_prompt p, ' '405/^y/i === get_input_line406ensure407update_prompt *old_p408end409410#411# Handle prompt substitutions412#413def format_prompt(str)414return str unless framework415416# find the active session417session = framework.sessions.values.find { |session| session.interacting }418default = 'unknown'419420formatted = ''421skip_next = false422for prefix, spec in str.split('').each_cons(2) do423if skip_next424skip_next = false425next426end427428unless prefix == '%'429formatted << prefix430skip_next = false431next432end433434skip_next = true435if spec == 'T'436if framework.datastore['PromptTimeFormat']437strftime_format = framework.datastore['PromptTimeFormat']438else439strftime_format = ::Time::DATE_FORMATS[:db].to_s440end441formatted << ::Time.now.strftime(strftime_format).to_s442elsif spec == 'W' && framework.db.active443formatted << framework.db.workspace.name444elsif session445sysinfo = session.respond_to?(:sys) ? session.sys.config.sysinfo : nil446447case spec448when 'A'449formatted << (sysinfo.nil? ? default : sysinfo['Architecture'])450when 'D'451formatted << (session.respond_to?(:fs) ? session.fs.dir.getwd(refresh: false) : default)452when 'd'453formatted << ::Dir.getwd454when 'H'455formatted << (sysinfo.nil? ? default : sysinfo['Computer'])456when 'h'457formatted << (self.local_hostname || default).chomp458when 'I'459formatted << session.tunnel_peer460when 'i'461formatted << session.tunnel_local462when 'M'463formatted << session.session_type464when 'S'465formatted << session.sid.to_s466when 'U'467formatted << (session.respond_to?(:sys) ? session.sys.config.getuid(refresh: false) : default)468when 'u'469formatted << (self.local_username || default).chomp470else471formatted << prefix472skip_next = false473end474else475case spec476when 'H'477formatted << (self.local_hostname || default).chomp478when 'J'479formatted << framework.jobs.length.to_s480when 'U'481formatted << (self.local_username || default).chomp482when 'S'483formatted << framework.sessions.length.to_s484when 'L'485formatted << Rex::Socket.source_address486when 'D'487formatted << ::Dir.getwd488else489formatted << prefix490skip_next = false491end492end493end494495if str.length > 0 && !skip_next496formatted << str[-1]497end498499formatted500end501502attr_writer :input, :output # :nodoc:503attr_writer :prompt, :prompt_char # :nodoc:504attr_accessor :stop_flag, :cont_prompt # :nodoc:505attr_accessor :tab_complete_proc # :nodoc:506attr_accessor :histfile # :nodoc:507attr_accessor :log_source, :stop_count # :nodoc:508attr_accessor :local_hostname, :local_username # :nodoc:509attr_reader :cont_flag # :nodoc:510attr_accessor :name511private512513def try_exec(command)514begin515%x{ #{ command } }516rescue SystemCallError517nil518end519end520521attr_writer :cont_flag # :nodoc:522523end524525end end end526527528