2024-01-31 14:21:03 +00:00
|
|
|
# -*- coding: binary -*-
|
|
|
|
|
module Rex
|
|
|
|
|
module Post
|
2024-02-06 11:43:44 +00:00
|
|
|
module Sql
|
2024-01-31 14:21:03 +00:00
|
|
|
module Ui
|
2024-02-06 11:43:44 +00:00
|
|
|
module Console
|
2024-01-31 14:21:03 +00:00
|
|
|
|
|
|
|
|
###
|
|
|
|
|
#
|
|
|
|
|
# Mixin that is meant to extend a sql client class in a
|
|
|
|
|
# manner that adds interactive capabilities.
|
|
|
|
|
#
|
|
|
|
|
###
|
2024-02-06 11:43:44 +00:00
|
|
|
module InteractiveSqlClient
|
2024-01-31 14:21:03 +00:00
|
|
|
|
|
|
|
|
include Rex::Ui::Interactive
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# Interacts with self.
|
|
|
|
|
#
|
|
|
|
|
def _interact
|
|
|
|
|
while self.interacting
|
|
|
|
|
sql_input = _multiline_with_fallback
|
|
|
|
|
self.interacting = (sql_input[:status] != :exit)
|
2024-03-06 16:32:16 +00:00
|
|
|
|
|
|
|
|
if sql_input[:status] == :help
|
|
|
|
|
client_dispatcher.query_interactive_help
|
|
|
|
|
end
|
|
|
|
|
|
2024-01-31 14:21:03 +00:00
|
|
|
# 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 && sql_input[:result])
|
|
|
|
|
|
|
|
|
|
self.on_command_proc.call(sql_input[:result].strip) if self.on_command_proc
|
|
|
|
|
|
|
|
|
|
formatted_query = client_dispatcher.process_query(query: sql_input[:result])
|
|
|
|
|
print_status "Executing query: #{formatted_query}"
|
|
|
|
|
client_dispatcher.cmd_query(formatted_query)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# Called when an interrupt is sent.
|
|
|
|
|
#
|
|
|
|
|
def _interrupt
|
|
|
|
|
prompt_yesno('Terminate interactive SQL prompt?')
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# Suspends interaction with the interactive REPL interpreter
|
|
|
|
|
#
|
|
|
|
|
def _suspend
|
|
|
|
|
if (prompt_yesno('Background interactive SQL prompt?') == 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
|
|
|
|
|
|
|
|
|
|
def _winch
|
|
|
|
|
# noop
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# Try getting multi-line input support provided by Reline, fall back to Readline.
|
|
|
|
|
def _multiline_with_fallback
|
|
|
|
|
query = _multiline
|
|
|
|
|
query = _fallback if query[:status] == :fail
|
|
|
|
|
|
|
|
|
|
query
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def _multiline
|
|
|
|
|
begin
|
|
|
|
|
require 'reline' unless defined?(::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
|
2024-03-06 16:32:16 +00:00
|
|
|
help_words = %w[help h].freeze
|
2024-01-31 14:21:03 +00:00
|
|
|
|
|
|
|
|
finished = false
|
2024-03-06 16:32:16 +00:00
|
|
|
help = false
|
2024-01-31 14:21:03 +00:00
|
|
|
begin
|
2024-02-27 16:59:38 +00:00
|
|
|
result = nil
|
2024-01-31 14:21:03 +00:00
|
|
|
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
|
2024-02-26 16:49:15 +00:00
|
|
|
# multiline_input is the whole string that the user has input, not just the current line.
|
2024-01-31 14:21:03 +00:00
|
|
|
raw_query = ::Reline.readmultiline('SQL >> ', use_history = true) do |multiline_input|
|
|
|
|
|
# The user pressed ctrl + c or ctrl + z and wants to background our SQL prompt
|
2024-02-27 16:59:38 +00:00
|
|
|
unless self.interacting
|
|
|
|
|
result = { status: :exit, result: nil }
|
|
|
|
|
next true
|
|
|
|
|
end
|
|
|
|
|
|
2024-02-26 16:49:15 +00:00
|
|
|
# When the user has pressed the enter key with no input, don't run any queries;
|
|
|
|
|
# simply give them a new prompt on a new line.
|
|
|
|
|
if multiline_input.chomp.empty?
|
2024-02-27 16:59:38 +00:00
|
|
|
result = { status: :success, result: nil }
|
|
|
|
|
next true
|
2024-02-26 16:49:15 +00:00
|
|
|
end
|
2024-01-31 14:21:03 +00:00
|
|
|
|
2024-03-06 16:32:16 +00:00
|
|
|
if multiline_input.split.count == 1
|
|
|
|
|
# In the case only a stop word was input, exit out of the REPL shell
|
|
|
|
|
finished = stop_words.include?(multiline_input.split.last)
|
|
|
|
|
# In the case when only a help word was input call the help command
|
|
|
|
|
help = help_words.include?(multiline_input.split.last)
|
|
|
|
|
end
|
2024-01-31 14:21:03 +00:00
|
|
|
|
2024-03-07 11:00:29 +00:00
|
|
|
finished || help || multiline_input.split.last&.end_with?(';')
|
2024-01-31 14:21:03 +00:00
|
|
|
end
|
|
|
|
|
rescue ::StandardError => e
|
|
|
|
|
elog('Failed to get multi-line SQL query from user', e)
|
|
|
|
|
ensure
|
|
|
|
|
::Reline.prompt_proc = prompt_proc_before
|
|
|
|
|
end
|
|
|
|
|
|
2024-02-27 16:59:38 +00:00
|
|
|
if result
|
|
|
|
|
return result
|
|
|
|
|
end
|
|
|
|
|
|
2024-03-06 16:32:16 +00:00
|
|
|
if help
|
|
|
|
|
return { status: :help, result: nil }
|
|
|
|
|
end
|
|
|
|
|
|
2024-01-31 14:21:03 +00:00
|
|
|
if finished
|
|
|
|
|
self.interacting = false
|
2024-02-26 11:23:37 -06:00
|
|
|
print_status 'Exiting Interactive mode.'
|
2024-01-31 14:21:03 +00:00
|
|
|
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
|
|
|
|
|
self.interacting = false
|
2024-02-26 11:23:37 -06:00
|
|
|
print_status 'Exiting Interactive mode.'
|
2024-01-31 14:21:03 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
attr_accessor :on_log_proc, :client_dispatcher
|
|
|
|
|
|
|
|
|
|
end
|
2024-02-06 11:43:44 +00:00
|
|
|
end
|
2024-01-31 14:21:03 +00:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|