forked from rapid7/metasploit-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support CTRL + Z for interactive SQL REPL
- Loading branch information
1 parent
288d4b8
commit b0fc2f7
Showing
3 changed files
with
185 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
160 changes: 160 additions & 0 deletions
160
lib/rex/post/postgresql/ui/console/interactive_channel.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
# -*- coding: binary -*- | ||
module Rex | ||
module Post | ||
module PostgreSQL | ||
module Ui | ||
|
||
### | ||
# | ||
# Mixin that is meant to extend the base channel class from meterpreter in a | ||
# manner that adds interactive capabilities. | ||
# | ||
### | ||
module Console::InteractiveChannel | ||
|
||
include Rex::Ui::Interactive | ||
|
||
# | ||
# Interacts with self. | ||
# | ||
def _interact | ||
begin | ||
while self.interacting | ||
raw_sql_string = _get_user_input | ||
# We need to check that the user is still interacting, i.e. if ctrl+z is triggered when requesting user input | ||
break unless (self.interacting && raw_sql_string) | ||
|
||
formatted_query = client_dispatcher.process_query(query: raw_sql_string) | ||
print_status "Executing query: #{formatted_query}" | ||
client_dispatcher.cmd_query(formatted_query) | ||
end | ||
rescue ::StandardError => e | ||
elog('Error when interact with SQL REPL', e) | ||
raise e | ||
end | ||
end | ||
|
||
# | ||
# Called when an interrupt is sent. | ||
# | ||
def _interrupt | ||
prompt_yesno('Terminate interactive REPL interpreter?') | ||
end | ||
|
||
# | ||
# Suspends interaction with the interactive REPL interpreter | ||
# | ||
def _suspend | ||
if (prompt_yesno('Background interactive REPL interpreter?') == true) | ||
self.interacting = false | ||
end | ||
end | ||
|
||
# | ||
# We don't need to do any clean-up when finishing the interaction with the REPL | ||
# | ||
def _interact_complete | ||
# noop | ||
end | ||
|
||
# To have this be consistent between reline and the fallback, this will return a single string only. | ||
# Try getting multi-line input support provided by Reline, fall back to Readline. | ||
def _multiline_with_fallback | ||
begin | ||
query = _multiline | ||
query = _fallback if query[:status] == :fail | ||
rescue ::StandardError => e | ||
elog('Failed to get SQL input from user', e) | ||
raise e | ||
end | ||
|
||
query[:result] | ||
end | ||
|
||
def _multiline | ||
begin | ||
require 'reline' unless defined?(::Reline) | ||
user_input.extend(::Reline) unless user_input.kind_of?(::Reline) | ||
rescue ::LoadError => e | ||
elog('Failed to load Reline', e) | ||
return { status: :fail, errors: [e] } | ||
end | ||
|
||
stop_words = %w[stop s exit e end quit q].freeze | ||
|
||
finished = false | ||
begin | ||
prompt_proc_before = ::Reline.prompt_proc | ||
::Reline.prompt_proc = proc { |line_buffer| line_buffer.each_with_index.map { |_line, i| i > 0 ? 'SQL *> ' : 'SQL >> ' } } | ||
|
||
# We want to do this in a loop | ||
raw_query = user_input.send(:readmultiline, 'SQL >> ', use_history = true) do |multiline_input| | ||
# The user pressed ctrl + c or ctrl + z and wants to background our SQL prompt | ||
return { status: :exit, result: nil } unless self.interacting | ||
|
||
# In the case only a stop word was input, exit out of the REPL shell | ||
finished = (multiline_input.split.count == 1 && stop_words.include?(multiline_input.split.last)) | ||
|
||
finished || multiline_input.split.last&.end_with?(';') | ||
end | ||
rescue ::StandardError => e | ||
elog('Failed to get multi-line SQL query from user', e) | ||
ensure | ||
::Reline.prompt_proc = prompt_proc_before | ||
end | ||
|
||
if finished | ||
print_status 'Exiting Shell mode.' | ||
self.interacting = false | ||
return { status: :exit, result: nil } | ||
end | ||
|
||
{ status: :success, result: raw_query } | ||
end | ||
|
||
def _fallback | ||
stop_words = %w[stop s exit e end quit q].freeze | ||
line_buffer = [] | ||
while (line = ::Readline.readline(prompt = line_buffer.empty? ? 'SQL >> ' : 'SQL *> ', add_history = true)) | ||
return { status: :exit, result: nil } unless self.interacting | ||
|
||
if stop_words.include? line.chomp.downcase | ||
print_status 'Exiting shell mode.' | ||
self.interacting = false | ||
return { status: :exit, result: nil } | ||
end | ||
|
||
next if line.empty? | ||
|
||
line_buffer.append line | ||
|
||
break if line.end_with? ';' | ||
end | ||
|
||
{ status: :success, result: line_buffer.join } | ||
end | ||
|
||
# | ||
# Reads data from local input | ||
# | ||
def _get_user_input | ||
data = _multiline_with_fallback | ||
return unless data | ||
|
||
self.on_command_proc.call(data.strip) if self.on_command_proc | ||
|
||
data | ||
end | ||
|
||
def _winch | ||
# noop ? | ||
end | ||
|
||
attr_accessor :on_log_proc, :client_dispatcher | ||
|
||
end | ||
|
||
end | ||
end | ||
end | ||
end |