Skip to content

Commit 873d350

Browse files
committed
SQL sessions have correct history manager support
1 parent abb861c commit 873d350

File tree

2 files changed

+98
-21
lines changed

2 files changed

+98
-21
lines changed

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,19 @@ def _winch
6767

6868
# Try getting multi-line input support provided by Reline, fall back to Readline.
6969
def _multiline_with_fallback
70-
query = _multiline
71-
query = _fallback if query[:status] == :fail
70+
name = session.type
71+
query = {}
72+
73+
# Multiline (Reline) and fallback (Readline) have separate history contexts as they are two different libraries.
74+
framework.history_manager.with_context(history_file: Msf::Config.send("#{name}_session_history_interactive"), name: name, input_library: :reline) do
75+
query = _multiline
76+
end
77+
78+
if query[:status] == :fail
79+
framework.history_manager.with_context(history_file: Msf::Config.send("#{name}_session_history_interactive"), name: name, input_library: :readline) do
80+
query = _fallback
81+
end
82+
end
7283

7384
query
7485
end
@@ -163,6 +174,16 @@ def _fallback
163174

164175
attr_accessor :on_log_proc, :client_dispatcher
165176

177+
private
178+
179+
def framework
180+
client_dispatcher.shell.framework
181+
end
182+
183+
def session
184+
client_dispatcher.shell.session
185+
end
186+
166187
end
167188
end
168189
end

lib/rex/ui/text/shell/history_manager.rb

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ def initialize
2424
#
2525
# @param [String,nil] history_file The file to load and persist commands to
2626
# @param [String] name Human readable history context name
27+
# @param [Symbol] input_library The input library to provide context for. :reline, :readline
2728
# @param [Proc] block
2829
# @return [nil]
29-
def with_context(history_file: nil, name: nil, &block)
30-
push_context(history_file: history_file, name: name)
30+
def with_context(history_file: nil, name: nil, input_library: nil, &block)
31+
input_library ||= :readline # Default to Readline for backwards compatibility.
32+
push_context(history_file: history_file, name: name, input_library: input_library)
3133

3234
begin
3335
block.call
@@ -65,9 +67,9 @@ def debug?
6567
@debug
6668
end
6769

68-
def push_context(history_file: nil, name: nil)
70+
def push_context(history_file: nil, name: nil, input_library: nil)
6971
$stderr.puts("Push context before\n#{JSON.pretty_generate(_contexts)}") if debug?
70-
new_context = { history_file: history_file, name: name }
72+
new_context = { history_file: history_file, name: name, input_library: input_library }
7173

7274
switch_context(new_context, @contexts.last)
7375
@contexts.push(new_context)
@@ -91,47 +93,91 @@ def readline_available?
9193
defined?(::Readline)
9294
end
9395

96+
def reline_available?
97+
begin
98+
require 'reline'
99+
defined?(::Reline)
100+
rescue ::LoadError => _e
101+
false
102+
end
103+
end
104+
94105
def clear_readline
95106
return unless readline_available?
96107

97108
::Readline::HISTORY.length.times { ::Readline::HISTORY.pop }
98109
end
99110

100-
def load_history_file(history_file)
101-
return unless readline_available?
111+
def clear_reline
112+
return unless reline_available?
102113

103-
clear_readline
104-
if File.exist?(history_file)
105-
File.readlines(history_file).each do |e|
106-
::Readline::HISTORY << e.chomp
114+
::Reline::HISTORY.length.times { ::Reline::HISTORY.pop }
115+
end
116+
117+
def load_history_file(context)
118+
history_file = context[:history_file]
119+
case context[:input_library]
120+
when :readline
121+
return unless readline_available?
122+
123+
clear_readline
124+
if File.exist?(history_file)
125+
File.readlines(history_file).each do |e|
126+
::Readline::HISTORY << safe_undump(e.chomp)
127+
end
128+
end
129+
when :reline
130+
return unless reline_available?
131+
132+
clear_reline
133+
if File.exist?(history_file)
134+
File.readlines(history_file).each do |e|
135+
::Reline::HISTORY << safe_undump(e.chomp)
136+
end
107137
end
108138
end
109139
end
110140

111-
def store_history_file(history_file)
112-
return unless readline_available?
141+
def store_history_file(context)
113142
cmds = []
114-
history_diff = ::Readline::HISTORY.length < MAX_HISTORY ? ::Readline::HISTORY.length : MAX_HISTORY
115-
history_diff.times do
116-
entry = ::Readline::HISTORY.pop
117-
cmds.push(entry) unless entry.nil?
143+
history_file = context[:history_file]
144+
145+
case context[:input_library]
146+
when :readline
147+
return unless readline_available?
148+
149+
history_diff = ::Readline::HISTORY.length < MAX_HISTORY ? ::Readline::HISTORY.length : MAX_HISTORY
150+
history_diff.times do
151+
entry = ::Readline::HISTORY.pop.dump
152+
cmds.push(entry) unless entry.nil?
153+
end
154+
when :reline
155+
return unless reline_available?
156+
157+
history_diff = ::Reline::HISTORY.length < MAX_HISTORY ? ::Reline::HISTORY.length : MAX_HISTORY
158+
history_diff.times do
159+
entry = ::Reline::HISTORY.pop.dump
160+
cmds.push(entry) unless entry.nil?
161+
end
118162
end
119163

120164
write_history_file(history_file, cmds)
121165
end
122166

123167
def switch_context(new_context, old_context=nil)
124168
if old_context && old_context[:history_file]
125-
store_history_file(old_context[:history_file])
169+
store_history_file(old_context)
126170
end
127171

128172
if new_context && new_context[:history_file]
129-
load_history_file(new_context[:history_file])
173+
load_history_file(new_context)
130174
else
131175
clear_readline
176+
clear_reline
132177
end
133-
rescue SignalException => e
178+
rescue SignalException => _e
134179
clear_readline
180+
clear_reline
135181
end
136182

137183
def write_history_file(history_file, cmds)
@@ -159,6 +205,16 @@ def write_history_file(history_file, cmds)
159205
@write_queue << event
160206
@remaining_work << event
161207
end
208+
209+
# @param [String] dumped A string that has been previously dumped
210+
# @return [String] A string that is undumped if possible or the input if it can't be undumped.
211+
def safe_undump(dumped)
212+
begin
213+
dumped.undump
214+
rescue ::RuntimeError => _e
215+
dumped
216+
end
217+
end
162218
end
163219

164220
end

0 commit comments

Comments
 (0)