Skip to content

Commit e614e90

Browse files
authored
Merge pull request rapid7#19526 from rapid7/revert-19397-replace-readline-with-reline
Revert "Replace Readline with Reline"
2 parents c7d1e34 + a31261e commit e614e90

File tree

25 files changed

+300
-130
lines changed

25 files changed

+300
-130
lines changed

lib/metasploit/framework/command/console.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ def driver_options
9292
driver_options['ModulePath'] = options.modules.path
9393
driver_options['Plugins'] = options.console.plugins
9494
driver_options['Readline'] = options.console.readline
95+
driver_options['RealReadline'] = options.console.real_readline
9596
driver_options['Resource'] = options.console.resources
9697
driver_options['XCommands'] = options.console.commands
9798

lib/metasploit/framework/parsed_options/console.rb

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def options
1616
options.console.plugins = []
1717
options.console.quiet = false
1818
options.console.readline = true
19+
options.console.real_readline = false
1920
options.console.resources = []
2021
options.console.subcommand = :run
2122
}
@@ -53,11 +54,7 @@ def option_parser
5354
end
5455

5556
option_parser.on('-L', '--real-readline', 'Use the system Readline library instead of RbReadline') do
56-
message = "The RealReadline option has been marked as deprecated, and is currently a noop.\n"
57-
message << "Metasploit Framework now uses Reline exclusively as the input handling library.\n"
58-
message << "If you require this functionality, please use the following link to tell us:\n"
59-
message << ' https://github.com/rapid7/metasploit-framework/issues/19399'
60-
warn message
57+
options.console.real_readline = true
6158
end
6259

6360
option_parser.on('-o', '--output FILE', 'Output to the specified file') do |file|

lib/metasploit/framework/parsed_options/remote_db.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def options
1313
options.console.local_output = nil
1414
options.console.plugins = []
1515
options.console.quiet = false
16+
options.console.real_readline = false
1617
options.console.resources = []
1718
options.console.subcommand = :run
1819
}

lib/msf/ui/console/command_dispatcher/core.rb

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -760,7 +760,7 @@ def cmd_features_tabs(_str, words)
760760
end
761761

762762
def cmd_history(*args)
763-
length = ::Reline::HISTORY.length
763+
length = Readline::HISTORY.length
764764

765765
if length < @history_limit
766766
limit = length
@@ -780,7 +780,14 @@ def cmd_history(*args)
780780
limit = val.to_i
781781
end
782782
when '-c'
783-
::Reline::HISTORY.clear
783+
if Readline::HISTORY.respond_to?(:clear)
784+
Readline::HISTORY.clear
785+
elsif defined?(RbReadline)
786+
RbReadline.clear_history
787+
else
788+
print_error('Could not clear history, skipping file')
789+
return false
790+
end
784791

785792
# Portable file truncation?
786793
if File.writable?(Msf::Config.history_file)
@@ -801,7 +808,7 @@ def cmd_history(*args)
801808

802809
(start..length-1).each do |pos|
803810
cmd_num = (pos + 1).to_s
804-
print_line "#{cmd_num.ljust(pad_len)} #{::Reline::HISTORY[pos]}"
811+
print_line "#{cmd_num.ljust(pad_len)} #{Readline::HISTORY[pos]}"
805812
end
806813
end
807814

lib/msf/ui/console/command_dispatcher/developer.rb

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,6 @@ def cmd_irb(*args)
131131

132132
framework.history_manager.with_context(name: :irb) do
133133
begin
134-
reline_autocomplete = Reline.autocompletion
135134
if active_module
136135
print_status("You are in #{active_module.fullname}\n")
137136
Rex::Ui::Text::IrbShell.new(active_module).run
@@ -141,8 +140,6 @@ def cmd_irb(*args)
141140
end
142141
rescue
143142
print_error("Error during IRB: #{$!}\n\n#{$@.join("\n")}")
144-
ensure
145-
Reline.autocompletion = reline_autocomplete if defined? reline_autocomplete
146143
end
147144
end
148145

@@ -518,10 +515,6 @@ def cmd_time_help
518515
private
519516

520517
def modified_files
521-
# Temporary work-around until Open3 gets fixed on Windows 11:
522-
# https://github.com/ruby/open3/issues/9
523-
return [] if Rex::Compat.is_cygwin || Rex::Compat.is_windows
524-
525518
# Using an array avoids shelling out, so we avoid escaping/quoting
526519
changed_files = %w[git diff --name-only]
527520
begin

lib/msf/ui/console/driver.rb

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ class Driver < Msf::Ui::Driver
5353
# @option opts [Boolean] 'AllowCommandPassthru' (true) Whether to allow
5454
# unrecognized commands to be executed by the system shell
5555
# @option opts [Boolean] 'Readline' (true) Whether to use the readline or not
56+
# @option opts [Boolean] 'RealReadline' (false) Whether to use the system's
57+
# readline library instead of RBReadline
5658
# @option opts [String] 'HistFile' (Msf::Config.history_file) Path to a file
5759
# where we can store command history
5860
# @option opts [Array<String>] 'Resources' ([]) A list of resource files to
@@ -62,6 +64,8 @@ class Driver < Msf::Ui::Driver
6264
# @option opts [Boolean] 'SkipDatabaseInit' (false) Whether to skip
6365
# connecting to the database and running migrations
6466
def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {})
67+
choose_readline(opts)
68+
6569
histfile = opts['HistFile'] || Msf::Config.history_file
6670

6771
begin
@@ -128,6 +132,14 @@ def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {
128132
# stack
129133
enstack_dispatcher(CommandDispatcher::Core)
130134

135+
# Report readline error if there was one..
136+
if !@rl_err.nil?
137+
print_error("***")
138+
print_error("* Unable to load readline: #{@rl_err}")
139+
print_error("* Falling back to RbReadLine")
140+
print_error("***")
141+
end
142+
131143
# Load the other "core" command dispatchers
132144
CommandDispatchers.each do |dispatcher_class|
133145
dispatcher = enstack_dispatcher(dispatcher_class)
@@ -311,11 +323,11 @@ def save_config
311323
# Saves the recent history to the specified file
312324
#
313325
def save_recent_history(path)
314-
num = ::Reline::HISTORY.length - hist_last_saved - 1
326+
num = Readline::HISTORY.length - hist_last_saved - 1
315327

316328
tmprc = ""
317329
num.times { |x|
318-
tmprc << ::Reline::HISTORY[hist_last_saved + x] + "\n"
330+
tmprc << Readline::HISTORY[hist_last_saved + x] + "\n"
319331
}
320332

321333
if tmprc.length > 0
@@ -327,7 +339,7 @@ def save_recent_history(path)
327339

328340
# Always update this, even if we didn't save anything. We do this
329341
# so that we don't end up saving the "makerc" command itself.
330-
self.hist_last_saved = ::Reline::HISTORY.length
342+
self.hist_last_saved = Readline::HISTORY.length
331343
end
332344

333345
#
@@ -690,6 +702,44 @@ def handle_session_tlv_logging(val)
690702

691703
false
692704
end
705+
706+
# Require the appropriate readline library based on the user's preference.
707+
#
708+
# @return [void]
709+
def choose_readline(opts)
710+
# Choose a readline library before calling the parent
711+
@rl_err = nil
712+
if opts['RealReadline']
713+
# Remove the gem version from load path to be sure we're getting the
714+
# stdlib readline.
715+
gem_dir = Gem::Specification.find_all_by_name('rb-readline').first.gem_dir
716+
rb_readline_path = File.join(gem_dir, "lib")
717+
index = $LOAD_PATH.index(rb_readline_path)
718+
# Bundler guarantees that the gem will be there, so it should be safe to
719+
# assume we found it in the load path, but check to be on the safe side.
720+
if index
721+
$LOAD_PATH.delete_at(index)
722+
end
723+
end
724+
725+
begin
726+
require 'readline'
727+
rescue ::LoadError => e
728+
if @rl_err.nil? && index
729+
# Then this is the first time the require failed and we have an index
730+
# for the gem version as a fallback.
731+
@rl_err = e
732+
# Put the gem back and see if that works
733+
$LOAD_PATH.insert(index, rb_readline_path)
734+
index = rb_readline_path = nil
735+
retry
736+
else
737+
# Either we didn't have the gem to fall back on, or we failed twice.
738+
# Nothing more we can do here.
739+
raise e
740+
end
741+
end
742+
end
693743
end
694744

695745
end

lib/msf/ui/console/module_option_tab_completion.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,18 @@ def tab_complete_option(mod, str, words)
5353
option_name = str.chop
5454
option_value = ''
5555

56-
::Reline.completion_append_character = ' '
56+
::Readline.completion_append_character = ' '
5757
return tab_complete_option_values(mod, option_value, words, opt: option_name).map { |value| "#{str}#{value}" }
5858
elsif str.include?('=')
5959
str_split = str.split('=')
6060
option_name = str_split[0].strip
6161
option_value = str_split[1].strip
6262

63-
::Reline.completion_append_character = ' '
63+
::Readline.completion_append_character = ' '
6464
return tab_complete_option_values(mod, option_value, words, opt: option_name).map { |value| "#{option_name}=#{value}" }
6565
end
6666

67-
::Reline.completion_append_character = ''
67+
::Readline.completion_append_character = ''
6868
tab_complete_option_names(mod, str, words).map { |name| "#{name}=" }
6969
end
7070

lib/msf/ui/debug.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,13 +220,13 @@ def self.framework_config(framework)
220220
end
221221

222222
def self.history(driver)
223-
end_pos = ::Reline::HISTORY.length - 1
223+
end_pos = Readline::HISTORY.length - 1
224224
start_pos = end_pos - COMMAND_HISTORY_TOTAL > driver.hist_last_saved ? end_pos - (COMMAND_HISTORY_TOTAL - 1) : driver.hist_last_saved
225225

226226
commands = ''
227227
while start_pos <= end_pos
228228
# Formats command position in history to 6 characters in length
229-
commands += "#{'%-6.6s' % start_pos.to_s} #{::Reline::HISTORY[start_pos]}\n"
229+
commands += "#{'%-6.6s' % start_pos.to_s} #{Readline::HISTORY[start_pos]}\n"
230230
start_pos += 1
231231
end
232232

lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,7 @@ def tab_complete_cdirectory(str, words)
902902

903903
def tab_complete_path(str, words, dir_only)
904904
if client.platform == 'windows'
905-
::Reline.completion_case_fold = true
905+
::Readline.completion_case_fold = true
906906
end
907907
if client.commands.include?(COMMAND_ID_STDAPI_FS_LS)
908908
expanded = str
@@ -915,7 +915,7 @@ def tab_complete_path(str, words, dir_only)
915915
# This is annoying if we're recursively tab-traversing our way through subdirectories -
916916
# we may want to continue traversing, but MSF will add a space, requiring us to back up to continue
917917
# tab-completing our way through successive subdirectories.
918-
::Reline.completion_append_character = nil
918+
::Readline.completion_append_character = nil
919919
end
920920
results
921921
else
@@ -939,6 +939,7 @@ def unexpand_path_for_suggestions(original_path, expanded_path, suggestions)
939939
result
940940
end
941941
end
942+
942943
end
943944

944945
end

lib/rex/post/sql/ui/console/interactive_sql_client.rb

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ module InteractiveSqlClient
2020
#
2121
def _interact
2222
while self.interacting
23-
sql_input = reline_multiline
23+
sql_input = _multiline_with_fallback
2424
self.interacting = (sql_input[:status] != :exit)
2525

2626
if sql_input[:status] == :help
@@ -65,21 +65,35 @@ def _winch
6565
# noop
6666
end
6767

68-
# Get multi-line input support provided by Reline.
69-
def reline_multiline
68+
# Try getting multi-line input support provided by Reline, fall back to Readline.
69+
def _multiline_with_fallback
7070
name = session.type
7171
query = {}
7272
history_file = Msf::Config.history_file_for_session_type(session_type: name, interactive: true)
7373
return { status: :fail, errors: ["Unable to get history file for session type: #{name}"] } if history_file.nil?
7474

75-
framework.history_manager.with_context(history_file: history_file , name: name) do
75+
# Multiline (Reline) and fallback (Readline) have separate history contexts as they are two different libraries.
76+
framework.history_manager.with_context(history_file: history_file , name: name, input_library: :reline) do
7677
query = _multiline
7778
end
7879

80+
if query[:status] == :fail
81+
framework.history_manager.with_context(history_file: history_file, name: name, input_library: :readline) do
82+
query = _fallback
83+
end
84+
end
85+
7986
query
8087
end
8188

8289
def _multiline
90+
begin
91+
require 'reline' unless defined?(::Reline)
92+
rescue ::LoadError => e
93+
elog('Failed to load Reline', e)
94+
return { status: :fail, errors: [e] }
95+
end
96+
8397
stop_words = %w[stop s exit e end quit q].freeze
8498
help_words = %w[help h].freeze
8599

@@ -138,6 +152,28 @@ def _multiline
138152
{ status: :success, result: raw_query }
139153
end
140154

155+
def _fallback
156+
stop_words = %w[stop s exit e end quit q].freeze
157+
line_buffer = []
158+
while (line = ::Readline.readline(prompt = line_buffer.empty? ? 'SQL >> ' : 'SQL *> ', add_history = true))
159+
return { status: :exit, result: nil } unless self.interacting
160+
161+
if stop_words.include? line.chomp.downcase
162+
self.interacting = false
163+
print_status 'Exiting Interactive mode.'
164+
return { status: :exit, result: nil }
165+
end
166+
167+
next if line.empty?
168+
169+
line_buffer.append line
170+
171+
break if line.end_with? ';'
172+
end
173+
174+
{ status: :success, result: line_buffer.join(' ') }
175+
end
176+
141177
attr_accessor :on_log_proc, :client_dispatcher
142178

143179
private

0 commit comments

Comments
 (0)