Skip to content

Commit 60c09e5

Browse files
committed
SQL sessions have correct history manager support
1 parent d37a825 commit 60c09e5

File tree

4 files changed

+262
-43
lines changed

4 files changed

+262
-43
lines changed

lib/msf/base/config.rb

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ def self.get_config_root
7474
'PluginDirectory' => "plugins",
7575
'DataDirectory' => "data",
7676
'LootDirectory' => "loot",
77-
'LocalDirectory' => "local"
77+
'LocalDirectory' => "local",
78+
'HistoriesDirectory' => "histories"
7879
}
7980

8081
##
@@ -97,6 +98,13 @@ def self.config_directory
9798
self.new.config_directory
9899
end
99100

101+
# Returns the histories directory default.
102+
#
103+
# @return [String] the SQL session histories directory.
104+
def self.histories_directory
105+
self.new.histories_directory
106+
end
107+
100108
# Return the directory that logo files should be loaded from.
101109
#
102110
# @return [String] path to the logos directory.
@@ -235,20 +243,41 @@ def self.postgresql_session_history
235243
self.new.postgresql_session_history
236244
end
237245

246+
# Returns the full path to the PostgreSQL interactive query history file
247+
#
248+
# @return [String] path to the interactive query history file.
249+
def self.postgresql_session_interactive_history
250+
self.new.postgresql_session_interactive_history
251+
end
252+
238253
# Returns the full path to the MSSQL session history file.
239254
#
240255
# @return [String] path to the history file.
241256
def self.mssql_session_history
242257
self.new.mssql_session_history
243258
end
244259

260+
# Returns the full path to the MSSQL interactive query history file
261+
#
262+
# @return [String] path to the interactive query history file.
263+
def self.mssql_session_interactive_history
264+
self.new.mssql_session_interactive_history
265+
end
266+
245267
# Returns the full path to the MySQL session history file.
246268
#
247269
# @return [String] path to the history file.
248270
def self.mysql_session_history
249271
self.new.mysql_session_history
250272
end
251273

274+
# Returns the full path to the MySQL interactive query history file
275+
#
276+
# @return [String] path to the interactive query history file.
277+
def self.mysql_session_interactive_history
278+
self.new.mysql_session_interactive_history
279+
end
280+
252281
def self.pry_history
253282
self.new.pry_history
254283
end
@@ -336,6 +365,13 @@ def config_directory
336365
self['ConfigDirectory']
337366
end
338367

368+
# Returns the histories directory default.
369+
#
370+
# @return [String] the SQL session histories directory.
371+
def histories_directory
372+
config_directory + FileSep + self['HistoriesDirectory']
373+
end
374+
339375
# Returns the full path to the configuration file.
340376
#
341377
# @return [String] path to the configuration file.
@@ -363,15 +399,27 @@ def ldap_session_history
363399
end
364400

365401
def postgresql_session_history
366-
config_directory + FileSep + "postgresql_session_history"
402+
histories_directory + FileSep + "postgresql_session_history"
403+
end
404+
405+
def postgresql_session_interactive_history
406+
histories_directory + FileSep + "postgresql_session_interactive_history"
367407
end
368408

369409
def mysql_session_history
370-
config_directory + FileSep + "mysql_session_history"
410+
histories_directory + FileSep + "mysql_session_history"
411+
end
412+
413+
def mysql_session_interactive_history
414+
histories_directory + FileSep + "mysql_session_interactive_history"
371415
end
372416

373417
def mssql_session_history
374-
config_directory + FileSep + "mssql_session_history"
418+
histories_directory + FileSep + "mssql_session_history"
419+
end
420+
421+
def mssql_session_interactive_history
422+
histories_directory + FileSep + "mssql_session_interactive_history"
375423
end
376424

377425
def pry_history
@@ -495,6 +543,7 @@ def init
495543
FileUtils.mkdir_p(user_module_directory)
496544
FileUtils.mkdir_p(user_plugin_directory)
497545
FileUtils.mkdir_p(user_data_directory)
546+
FileUtils.mkdir_p(histories_directory)
498547
end
499548

500549
# Loads configuration from the supplied file path, or the default one if

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

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,20 @@ 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+
history_file = Msf::Config.send("#{name}_session_interactive_history")
73+
74+
# Multiline (Reline) and fallback (Readline) have separate history contexts as they are two different libraries.
75+
framework.history_manager.with_context(history_file: history_file , name: name, input_library: :reline) do
76+
query = _multiline
77+
end
78+
79+
if query[:status] == :fail
80+
framework.history_manager.with_context(history_file: history_file, name: name, input_library: :readline) do
81+
query = _fallback
82+
end
83+
end
7284

7385
query
7486
end
@@ -158,11 +170,21 @@ def _fallback
158170
break if line.end_with? ';'
159171
end
160172

161-
{ status: :success, result: line_buffer.join }
173+
{ status: :success, result: line_buffer.join(' ') }
162174
end
163175

164176
attr_accessor :on_log_proc, :client_dispatcher
165177

178+
private
179+
180+
def framework
181+
client_dispatcher.shell.framework
182+
end
183+
184+
def session
185+
client_dispatcher.shell.session
186+
end
187+
166188
end
167189
end
168190
end

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

Lines changed: 60 additions & 21 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+
# Default to Readline for backwards compatibility.
32+
push_context(history_file: history_file, name: name, input_library: input_library || :readline)
3133

3234
begin
3335
block.call
@@ -59,15 +61,21 @@ def _debug=(value)
5961
@debug = value
6062
end
6163

64+
# Return a queue of threads that have not yet finished saving the history file to disk.
65+
# @return [Queue] a queue of threads that have not yet finished saving the history file to disk.
66+
def _remaining_work
67+
@remaining_work
68+
end
69+
6270
private
6371

6472
def debug?
6573
@debug
6674
end
6775

68-
def push_context(history_file: nil, name: nil)
76+
def push_context(history_file: nil, name: nil, input_library: nil)
6977
$stderr.puts("Push context before\n#{JSON.pretty_generate(_contexts)}") if debug?
70-
new_context = { history_file: history_file, name: name }
78+
new_context = { history_file: history_file, name: name, input_library: input_library || :readline }
7179

7280
switch_context(new_context, @contexts.last)
7381
@contexts.push(new_context)
@@ -91,47 +99,78 @@ def readline_available?
9199
defined?(::Readline)
92100
end
93101

102+
def reline_available?
103+
begin
104+
require 'reline'
105+
defined?(::Reline)
106+
rescue ::LoadError => _e
107+
false
108+
end
109+
end
110+
94111
def clear_readline
95112
return unless readline_available?
96113

97114
::Readline::HISTORY.length.times { ::Readline::HISTORY.pop }
98115
end
99116

100-
def load_history_file(history_file)
101-
return unless readline_available?
117+
def clear_reline
118+
return unless reline_available?
102119

103-
clear_readline
104-
if File.exist?(history_file)
105-
File.readlines(history_file).each do |e|
106-
::Readline::HISTORY << e.chomp
107-
end
120+
::Reline::HISTORY.length.times { ::Reline::HISTORY.pop }
121+
end
122+
123+
def load_history_file(context)
124+
history_file = context[:history_file]
125+
history = context[:input_library] == :reline ? ::Reline::HISTORY : ::Readline::HISTORY
126+
127+
begin
128+
File.open(history_file, 'r') do |f|
129+
context[:input_library] == :reline ? clear_reline : clear_readline
130+
f.each do |line|
131+
chomped_line = line.chomp
132+
if context[:input_library] == :reline && history.last&.end_with?("\\")
133+
history.last.delete_suffix!("\\")
134+
history.last << "\n" << chomped_line
135+
else
136+
history << chomped_line
137+
end
138+
end
139+
end
140+
rescue Errno::EACCES, Errno::ENOENT => e
141+
$stderr.puts("Failed to open history file: #{history_file} with error: #{e}") if debug?
108142
end
109143
end
110144

111-
def store_history_file(history_file)
112-
return unless readline_available?
145+
def store_history_file(context)
146+
history_file = context[:history_file]
147+
history = context[:input_library] == :reline ? ::Reline::HISTORY : ::Readline::HISTORY
148+
149+
history_diff = history.length < MAX_HISTORY ? history.length : MAX_HISTORY
150+
113151
cmds = []
114-
history_diff = ::Readline::HISTORY.length < MAX_HISTORY ? ::Readline::HISTORY.length : MAX_HISTORY
115152
history_diff.times do
116-
entry = ::Readline::HISTORY.pop
117-
cmds.push(entry) unless entry.nil?
153+
entry = history.pop
154+
cmds << entry.scrub.split("\n").join("\\\n")
118155
end
119156

120-
write_history_file(history_file, cmds)
157+
write_history_file(history_file, cmds.reverse)
121158
end
122159

123160
def switch_context(new_context, old_context=nil)
124161
if old_context && old_context[:history_file]
125-
store_history_file(old_context[:history_file])
162+
store_history_file(old_context)
126163
end
127164

128165
if new_context && new_context[:history_file]
129-
load_history_file(new_context[:history_file])
166+
load_history_file(new_context)
130167
else
131168
clear_readline
169+
clear_reline
132170
end
133-
rescue SignalException => e
171+
rescue SignalException => _e
134172
clear_readline
173+
clear_reline
135174
end
136175

137176
def write_history_file(history_file, cmds)
@@ -144,7 +183,7 @@ def write_history_file(history_file, cmds)
144183
cmds = event[:cmds]
145184

146185
File.open(history_file, 'wb+') do |f|
147-
f.puts(cmds.reverse)
186+
f.puts(cmds)
148187
end
149188

150189
rescue => e

0 commit comments

Comments
 (0)