Module: Rex::Post::Sql::Ui::Console::InteractiveSqlClient

Includes:
Ui::Interactive
Defined in:
lib/rex/post/sql/ui/console/interactive_sql_client.rb

Overview

Mixin that is meant to extend a sql client class in a manner that adds interactive capabilities.

Instance Attribute Summary collapse

Attributes included from Ui::Interactive

#completed, #interacting, #next_session, #on_command_proc, #on_print_proc, #on_run_command_error_proc, #orig_suspend, #orig_usr1, #orig_winch

Attributes included from Ui::Subscriber::Input

#user_input

Attributes included from Ui::Subscriber::Output

#user_output

Instance Method Summary collapse

Methods included from Ui::Interactive

#_local_fd, #_remote_fd, #_stream_read_local_write_remote, #_stream_read_remote_write_local, #detach, #handle_suspend, #handle_usr1, #handle_winch, #interact, #interact_stream, #prompt, #prompt_yesno, #restore_suspend, #restore_usr1, #restore_winch

Methods included from Ui::Subscriber

#copy_ui, #init_ui, #reset_ui

Methods included from Ui::Subscriber::Input

#gets

Methods included from Ui::Subscriber::Output

#flush, #print, #print_blank_line, #print_error, #print_good, #print_line, #print_status, #print_warning

Instance Attribute Details

#client_dispatcherObject

Returns the value of attribute client_dispatcher.



177
178
179
# File 'lib/rex/post/sql/ui/console/interactive_sql_client.rb', line 177

def client_dispatcher
  @client_dispatcher
end

#on_log_procObject

Returns the value of attribute on_log_proc.



177
178
179
# File 'lib/rex/post/sql/ui/console/interactive_sql_client.rb', line 177

def on_log_proc
  @on_log_proc
end

Instance Method Details

#_fallbackObject



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/rex/post/sql/ui/console/interactive_sql_client.rb', line 155

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
      print_status 'Exiting Interactive mode.'
      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

#_interactObject

Interacts with self.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/rex/post/sql/ui/console/interactive_sql_client.rb', line 21

def _interact
  while self.interacting
    sql_input = _multiline_with_fallback
    self.interacting = (sql_input[:status] != :exit)

    if sql_input[:status] == :help
      client_dispatcher.query_interactive_help
    end

    # 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

#_interact_completeObject

We don’t need to do any clean-up when finishing the interaction with the REPL



60
61
62
# File 'lib/rex/post/sql/ui/console/interactive_sql_client.rb', line 60

def _interact_complete
  # noop
end

#_interruptObject

Called when an interrupt is sent.



44
45
46
# File 'lib/rex/post/sql/ui/console/interactive_sql_client.rb', line 44

def _interrupt
  prompt_yesno('Terminate interactive SQL prompt?')
end

#_multilineObject



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/rex/post/sql/ui/console/interactive_sql_client.rb', line 89

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
  help_words = %w[help h].freeze

  finished = false
  help = false
  begin
    result = nil
    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
    # multiline_input is the whole string that the user has input, not just the current line.
    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
      unless self.interacting
        result = { status: :exit, result: nil }
        next true
      end

      # 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?
        result = { status: :success, result: nil }
        next true
      end

      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

      finished || help || 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 result
    return result
  end

  if help
    return { status: :help, result: nil }
  end

  if finished
    self.interacting = false
    print_status 'Exiting Interactive mode.'
    return { status: :exit, result: nil }
  end

  { status: :success, result: raw_query }
end

#_multiline_with_fallbackObject

Try getting multi-line input support provided by Reline, fall back to Readline.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/rex/post/sql/ui/console/interactive_sql_client.rb', line 69

def _multiline_with_fallback
  name = session.type
  query = {}
  history_file = Msf::Config.history_file_for_session_type(session_type: name, interactive: true)
  return { status: :fail, errors: ["Unable to get history file for session type: #{name}"] } if history_file.nil?

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

  if query[:status] == :fail
    framework.history_manager.with_context(history_file: history_file, name: name, input_library: :readline) do
      query = _fallback
    end
  end

  query
end

#_suspendObject

Suspends interaction with the interactive REPL interpreter



51
52
53
54
55
# File 'lib/rex/post/sql/ui/console/interactive_sql_client.rb', line 51

def _suspend
  if (prompt_yesno('Background interactive SQL prompt?') == true)
    self.interacting = false
  end
end

#_winchObject



64
65
66
# File 'lib/rex/post/sql/ui/console/interactive_sql_client.rb', line 64

def _winch
  # noop
end