Module: Rex::Ui::Text::DispatcherShell
- Included in:
- Msf::Ui::Console::Driver, Post::HWBridge::Ui::Console, Post::LDAP::Ui::Console, Post::MSSQL::Ui::Console, Post::Meterpreter::Ui::Console, Post::MySQL::Ui::Console, Post::PostgreSQL::Ui::Console, Post::SMB::Ui::Console, Post::Sql::Ui::Console
- Defined in:
- lib/rex/ui/text/dispatcher_shell.rb
Overview
The dispatcher shell class is designed to provide a generic means of processing various shell commands that may be located in different modules or chunks of codes. These chunks are referred to as command dispatchers. The only requirement for command dispatchers is that they prefix every method that they wish to be mirrored as a command with the cmd_ prefix.
Defined Under Namespace
Modules: CommandDispatcher
Instance Attribute Summary collapse
-
#blocked ⇒ Object
:nodoc:.
-
#busy ⇒ Object
:nodoc:.
-
#dispatcher_stack ⇒ Object
:nodoc:.
Attributes included from Shell
#cont_flag, #cont_prompt, #disable_output, #framework, #hist_last_saved, #histfile, #history_manager, #input, #local_hostname, #local_username, #log_source, #name, #on_command_proc, #on_print_proc, #output, #prompt, #prompt_char, #stop_count, #stop_flag, #tab_complete_proc
Instance Method Summary collapse
-
#append_dispatcher(dispatcher) ⇒ Object
Adds the supplied dispatcher to the end of the dispatcher stack so that it doesn’t affect any enstack’d dispatchers.
-
#block_command(cmd) ⇒ Object
Block a specific command.
-
#blocked_command?(cmd) ⇒ Boolean
Returns nil for an empty set of blocked commands.
-
#current_dispatcher ⇒ Object
Returns the current active dispatcher.
-
#destack_dispatcher ⇒ Object
Pop a dispatcher from the front of the stacker.
-
#enstack_dispatcher(dispatcher) ⇒ Object
Push a dispatcher to the front of the stack.
-
#help_to_s(opts = {}) ⇒ Object
Return a readable version of a help banner for all of the enstacked dispatchers.
-
#initialize(prompt, prompt_char = '>', histfile = nil, framework = nil, name = nil) ⇒ Object
Initialize the dispatcher shell.
-
#remove_dispatcher(name) ⇒ Object
Removes the supplied dispatcher instance.
-
#run_command(dispatcher, method, arguments) ⇒ Object
Runs the supplied command on the given dispatcher.
-
#run_single(line, propagate_errors: false) ⇒ Boolean
Run a single command line.
-
#shellsplitex(line) ⇒ Object
Split a line as Shellwords.split would however instead of raising an ArgumentError on unbalanced quotes return the remainder of the string as if the last character were the closing quote.
-
#tab_complete(str) ⇒ Object
This method accepts the entire line of text from the Readline routine, stores all completed words, and passes the partial word to the real tab completion function.
-
#tab_complete_helper(dispatcher, str, words) ⇒ Object
Provide command-specific tab completion.
-
#tab_complete_stub(original_str, split_str) ⇒ Object
Performs tab completion of a command, if supported.
-
#unblock_command(cmd) ⇒ Object
Unblock a specific command.
-
#unknown_command(method, line) ⇒ Object
If the command is unknown…
Methods included from Shell
#_print_prompt, #format_prompt, #get_input_line, #init_tab_complete, #init_ui, #log_input, #log_output, #parse_line, #print, #print_error, #print_good, #print_line, #print_status, #print_warning, #prompt_yesno, #reset_ui, #run, #set_log_source, #stop, #stopped?, #supports_color?, #unset_log_source, #update_prompt, #with_history_manager_context
Methods included from Resource
Instance Attribute Details
#blocked ⇒ Object
:nodoc:
730 731 732 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 730 def blocked @blocked end |
#busy ⇒ Object
:nodoc:
729 730 731 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 729 def busy @busy end |
#dispatcher_stack ⇒ Object
:nodoc:
728 729 730 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 728 def dispatcher_stack @dispatcher_stack end |
Instance Method Details
#append_dispatcher(dispatcher) ⇒ Object
Adds the supplied dispatcher to the end of the dispatcher stack so that it doesn’t affect any enstack’d dispatchers.
623 624 625 626 627 628 629 630 631 632 633 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 623 def append_dispatcher(dispatcher) inst = dispatcher.new(self) self.dispatcher_stack.each { |disp| if (disp.name == inst.name) raise "Attempting to load already loaded dispatcher #{disp.name}" end } self.dispatcher_stack.push(inst) inst end |
#block_command(cmd) ⇒ Object
Block a specific command
681 682 683 684 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 681 def block_command(cmd) self.blocked ||= {} self.blocked[cmd] = true end |
#blocked_command?(cmd) ⇒ Boolean
Returns nil for an empty set of blocked commands.
673 674 675 676 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 673 def blocked_command?(cmd) return false if not self.blocked self.blocked.has_key?(cmd) end |
#current_dispatcher ⇒ Object
Returns the current active dispatcher
647 648 649 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 647 def current_dispatcher self.dispatcher_stack[0] end |
#destack_dispatcher ⇒ Object
Pop a dispatcher from the front of the stacker.
615 616 617 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 615 def destack_dispatcher self.dispatcher_stack.shift end |
#enstack_dispatcher(dispatcher) ⇒ Object
Push a dispatcher to the front of the stack.
606 607 608 609 610 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 606 def enstack_dispatcher(dispatcher) self.dispatcher_stack.unshift(inst = dispatcher.new(self)) inst end |
#help_to_s(opts = {}) ⇒ Object
Return a readable version of a help banner for all of the enstacked dispatchers.
See CommandDispatcher#help_to_s
657 658 659 660 661 662 663 664 665 666 667 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 657 def help_to_s(opts = {}) str = '' max_command_length = dispatcher_stack.flat_map { |dispatcher| dispatcher.commands.to_a }.map { |(name, _description)| name.length }.max dispatcher_stack.reverse.each { |dispatcher| str << dispatcher.help_to_s(opts.merge({ command_width: [max_command_length, 12].max })) } return str << "For more info on a specific command, use %grn<command> -h%clr or %grnhelp <command>%clr.\n\n" end |
#initialize(prompt, prompt_char = '>', histfile = nil, framework = nil, name = nil) ⇒ Object
Initialize the dispatcher shell.
394 395 396 397 398 399 400 401 402 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 394 def initialize(prompt, prompt_char = '>', histfile = nil, framework = nil, name = nil) super # Initialize the dispatcher array self.dispatcher_stack = [] # Initialize the tab completion array self.on_command_proc = nil end |
#remove_dispatcher(name) ⇒ Object
Removes the supplied dispatcher instance.
638 639 640 641 642 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 638 def remove_dispatcher(name) self.dispatcher_stack.delete_if { |inst| (inst.name == name) } end |
#run_command(dispatcher, method, arguments) ⇒ Object
Runs the supplied command on the given dispatcher.
576 577 578 579 580 581 582 583 584 585 586 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 576 def run_command(dispatcher, method, arguments) self.busy = true if(blocked_command?(method)) print_error("The #{method} command has been disabled.") else dispatcher.send('cmd_' + method, *arguments) end ensure self.busy = false end |
#run_single(line, propagate_errors: false) ⇒ Boolean
Run a single command line.
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 513 def run_single(line, propagate_errors: false) arguments = parse_line(line) method = arguments.shift cmd_status = nil # currently either nil or :handled, more statuses can be added in the future error = false # If output is disabled output will be nil output.reset_color if (output) if (method) entries = dispatcher_stack.length dispatcher_stack.each { |dispatcher| next if not dispatcher.respond_to?('commands') begin if (dispatcher.commands.has_key?(method) or dispatcher.deprecated_commands.include?(method)) self.on_command_proc.call(line.strip) if self.on_command_proc run_command(dispatcher, method, arguments) cmd_status = :handled elsif cmd_status.nil? cmd_status = dispatcher.unknown_command(method, line) end rescue ::Interrupt cmd_status = :handled print_error("#{method}: Interrupted") raise if propagate_errors rescue OptionParser::ParseError => e print_error("#{method}: #{e.}") raise if propagate_errors rescue error = $! print_error( "Error while running command #{method}: #{$!}" + "\n\nCall stack:\n#{$@.join("\n")}") raise if propagate_errors rescue ::Exception => e error = $! print_error( "Error while running command #{method}: #{$!}") raise if propagate_errors end # If the dispatcher stack changed as a result of this command, # break out break if (dispatcher_stack.length != entries) } if (cmd_status.nil? && error == false) unknown_command(method, line) end end return cmd_status == :handled end |
#shellsplitex(line) ⇒ Object
Split a line as Shellwords.split would however instead of raising an ArgumentError on unbalanced quotes return the remainder of the string as if the last character were the closing quote.
This code was originally taken from github.com/ruby/ruby/blob/93420d34aaf8c30f11a66dd08eb186da922c831d/lib/shellwords.rb#L88
701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 701 def shellsplitex(line) tokens = [] field_value = String.new field_begin = nil line.scan(/\G(\s*)(?>([^\s\\\'\"]+)|'([^\']*)'|"((?:[^\"\\]|\\.)*)"|(\\.?)|(\S))(\s|\z)?/m) do |preceding_whitespace, word, sq, dq, esc, garbage, sep| field_begin ||= Regexp.last_match.begin(0) + preceding_whitespace.length if garbage quote_start_begin = Regexp.last_match.begin(0) + preceding_whitespace.length field_quote = garbage field_value << line[quote_start_begin + 1..-1].gsub('\\\\', '\\') tokens << { begin: field_begin, value: field_value, quote: field_quote } break end field_value << (word || sq || (dq && dq.gsub(/\\([$`"\\\n])/, '\\1')) || esc.gsub(/\\(.)/, '\\1')) if sep tokens << { begin: field_begin, value: field_value, quote: ((sq && "'") || (dq && '"') || nil) } field_value = String.new field_begin = nil end end { tokens: tokens } end |
#tab_complete(str) ⇒ Object
This method accepts the entire line of text from the Readline routine, stores all completed words, and passes the partial word to the real tab completion function. This works around a design problem in the Readline module and depends on the Readline.basic_word_break_characters variable being set to x00
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 411 def tab_complete(str) ::Readline.completion_append_character = ' ' ::Readline.completion_case_fold = false # Check trailing whitespace so we can tell 'x' from 'x ' str_match = str.match(/[^\\]([\\]{2})*\s+$/) str_trail = (str_match.nil?) ? '' : str_match[0] # Split the line up by whitespace into words split_str = shellsplitex(str) # Append an empty token if we had trailing whitespace split_str[:tokens] << { begin: str.length, value: '' } if str_trail.length > 0 # Pop the last word and pass it to the real method result = tab_complete_stub(str, split_str) if result result.uniq else result end end |
#tab_complete_helper(dispatcher, str, words) ⇒ Object
Provide command-specific tab completion
492 493 494 495 496 497 498 499 500 501 502 503 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 492 def tab_complete_helper(dispatcher, str, words) tabs_meth = "cmd_#{words[0]}_tabs" # Is the user trying to tab complete one of our commands? if dispatcher.commands.include?(words[0]) and dispatcher.respond_to?(tabs_meth) res = dispatcher.send(tabs_meth, str, words) return [] if res.nil? return res end # Avoid the default completion list for unknown commands [] end |
#tab_complete_stub(original_str, split_str) ⇒ Object
Performs tab completion of a command, if supported
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 436 def tab_complete_stub(original_str, split_str) *preceding_tokens, current_token = split_str[:tokens] return nil unless current_token items = [] current_word = current_token[:value] tab_words = preceding_tokens.map { |word| word[:value] } # Next, try to match internal command or value completion # Enumerate each entry in the dispatcher stack dispatcher_stack.each do |dispatcher| # If no command is set and it supports commands, add them all if tab_words.empty? and dispatcher.respond_to?('commands') items.concat(dispatcher.commands.keys) end # If the dispatcher exports a tab completion function, use it if dispatcher.respond_to?('tab_complete_helper') res = dispatcher.tab_complete_helper(current_word, tab_words) else res = tab_complete_helper(dispatcher, current_word, tab_words) end if res.nil? # A nil response indicates no optional arguments return [''] if items.empty? else if res.second == :override_completions return res.first else # Otherwise we add the completion items to the list items.concat(res) end end end # Match based on the partial word matches = items.select do |word| word.downcase.start_with?(current_word.downcase) end # Prepend the preceding string of the command (or it all gets replaced!) preceding_str = original_str[0...current_token[:begin]] quote = current_token[:quote] matches_with_preceding_words_appended = matches.map do |word| word = quote.nil? ? word.gsub('\\') { '\\\\' }.gsub(' ', '\\ ') : "#{quote}#{word}#{quote}" preceding_str + word end matches_with_preceding_words_appended end |
#unblock_command(cmd) ⇒ Object
Unblock a specific command
689 690 691 692 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 689 def unblock_command(cmd) self.blocked || return self.blocked.delete(cmd) end |
#unknown_command(method, line) ⇒ Object
If the command is unknown…
591 592 593 594 595 596 597 598 599 600 601 |
# File 'lib/rex/ui/text/dispatcher_shell.rb', line 591 def unknown_command(method, line) # Map each dispatchers commands to valid_commands valid_commands = dispatcher_stack.flat_map { |dispatcher| dispatcher.commands.keys } = "Unknown command: #{method}." suggestion = DidYouMean::SpellChecker.new(dictionary: valid_commands).correct(method).first << " Did you mean %grn#{suggestion}%clr?" if suggestion << ' Run the %grnhelp%clr command for more details.' print_error() end |