CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/ui/interactive.rb
Views: 11780
1
# -*- coding: binary -*-
2
module Rex
3
module Ui
4
5
###
6
#
7
# This class implements the stubs that are needed to provide an interactive
8
# user interface that is backed against something arbitrary.
9
#
10
###
11
module Interactive
12
13
#
14
# Interactive sessions by default may interact with the local user input
15
# and output.
16
#
17
include Rex::Ui::Subscriber
18
19
#
20
# Starts interacting with the session at the most raw level, simply
21
# forwarding input from user_input to rstream and forwarding input from
22
# rstream to user_output.
23
#
24
def interact(user_input, user_output)
25
26
# Detach from any existing console
27
if self.interacting
28
detach()
29
end
30
31
init_ui(user_input, user_output)
32
33
self.interacting = true
34
self.completed = false
35
36
eof = false
37
38
# Start the readline stdin monitor
39
# XXX disabled
40
# user_input.readline_start() if user_input.supports_readline
41
42
# Handle suspend notifications
43
handle_suspend
44
45
handle_usr1
46
47
handle_winch
48
49
# As long as we're interacting...
50
while (self.interacting == true)
51
52
begin
53
_interact
54
55
rescue Interrupt
56
# If we get an interrupt exception, ask the user if they want to
57
# abort the interaction. If they do, then we return out of
58
# the interact function and call it a day.
59
eof = true if (_interrupt)
60
61
rescue EOFError, Errno::ECONNRESET, IOError
62
# If we reach EOF or the connection is reset...
63
eof = true
64
65
end
66
67
break if eof
68
end
69
70
begin
71
72
# Restore the suspend handler
73
restore_suspend
74
75
restore_winch
76
77
# If we've hit eof, call the interact complete handler
78
_interact_complete if (eof == true)
79
80
# Shutdown the readline thread
81
# XXX disabled
82
# user_input.readline_stop() if user_input.supports_readline
83
84
# Detach from the input/output handles
85
reset_ui()
86
87
ensure
88
# Mark this as completed
89
self.completed = true
90
end
91
92
# if another session was requested, store it
93
next_session = self.next_session
94
# clear the value from the object
95
self.next_session = nil
96
97
# return this session id
98
return next_session
99
end
100
101
#
102
# Stops the current interaction
103
#
104
def detach
105
if (self.interacting)
106
self.interacting = false
107
while(not self.completed)
108
::IO.select(nil, nil, nil, 0.25)
109
end
110
end
111
end
112
113
#
114
# Whether or not the session is currently being interacted with
115
#
116
attr_accessor :interacting
117
118
#
119
# If another session needs interaction, this is where it goes
120
#
121
attr_accessor :next_session
122
123
#
124
# Whether or not the session has completed interaction
125
#
126
attr_accessor :completed
127
128
attr_accessor :on_print_proc
129
attr_accessor :on_command_proc
130
131
#
132
# A function to be run when running a session command hits an error
133
#
134
# @return [Proc,nil] A function to be run when running a session command hits an error
135
attr_accessor :on_run_command_error_proc
136
137
protected
138
139
#
140
# The original suspend proc.
141
#
142
attr_accessor :orig_suspend
143
attr_accessor :orig_usr1
144
attr_accessor :orig_winch
145
146
#
147
# Stub method that is meant to handler interaction
148
#
149
def _interact
150
end
151
152
#
153
# Called when an interrupt is sent.
154
#
155
def _interrupt
156
true
157
end
158
159
#
160
# Called when a suspend is sent.
161
#
162
def _suspend
163
false
164
end
165
166
#
167
# Called when interaction has completed and one of the sides has closed.
168
#
169
def _interact_complete
170
true
171
end
172
173
#
174
# Read from remote and write to local.
175
#
176
def _stream_read_remote_write_local(stream)
177
data = stream.get
178
179
self.on_print_proc.call(data) if self.on_print_proc
180
user_output.print(data)
181
end
182
183
#
184
# Read from local and write to remote.
185
#
186
def _stream_read_local_write_remote(stream)
187
data = user_input.gets
188
189
self.on_command_proc.call(data) if self.on_command_proc
190
stream.put(data)
191
end
192
193
#
194
# The local file descriptor handle.
195
#
196
def _local_fd
197
user_input.fd
198
end
199
200
#
201
# The remote file descriptor handle.
202
#
203
def _remote_fd(stream)
204
stream.fd
205
end
206
207
#
208
# Interacts with two streaming connections, reading data from one and
209
# writing it to the other. Both are expected to implement Rex::IO::Stream.
210
#
211
def interact_stream(stream)
212
while self.interacting && _remote_fd(stream)
213
214
# Select input and rstream
215
sd = Rex::ThreadSafe.select([ _local_fd, _remote_fd(stream) ], nil, nil, 0.25)
216
217
# Cycle through the items that have data
218
# From the stream? Write to user_output.
219
sd[0].each { |s|
220
if (s == _remote_fd(stream))
221
_stream_read_remote_write_local(stream)
222
# From user_input? Write to stream.
223
elsif (s == _local_fd)
224
_stream_read_local_write_remote(stream)
225
end
226
} if (sd)
227
228
Thread.pass
229
end
230
end
231
232
233
#
234
# Installs a signal handler to monitor suspend signal notifications.
235
#
236
def handle_suspend
237
if orig_suspend.nil?
238
begin
239
self.orig_suspend = Signal.trap("TSTP") do
240
Thread.new { _suspend }.join
241
end
242
rescue
243
end
244
end
245
end
246
247
248
#
249
# Restores the previously installed signal handler for suspend
250
# notifications.
251
#
252
def restore_suspend
253
begin
254
if orig_suspend
255
Signal.trap("TSTP", orig_suspend)
256
else
257
Signal.trap("TSTP", "DEFAULT")
258
end
259
self.orig_suspend = nil
260
rescue
261
end
262
end
263
264
def handle_usr1
265
if orig_usr1.nil?
266
begin
267
self.orig_usr1 = Signal.trap("USR1") do
268
Thread.new { _usr1 }.join
269
end
270
rescue
271
end
272
end
273
end
274
275
def handle_winch
276
if orig_winch.nil?
277
begin
278
self.orig_winch = Signal.trap("WINCH") do
279
Thread.new { _winch }.join
280
end
281
rescue
282
end
283
end
284
end
285
286
def restore_winch
287
begin
288
if orig_winch
289
Signal.trap("WINCH", orig_winch)
290
else
291
Signal.trap("WINCH", "DEFAULT")
292
end
293
self.orig_winch = nil
294
rescue
295
end
296
end
297
298
def _winch
299
end
300
301
def restore_usr1
302
begin
303
if orig_usr1
304
Signal.trap("USR1", orig_usr1)
305
else
306
Signal.trap("USR1", "DEFAULT")
307
end
308
self.orig_usr1 = nil
309
rescue
310
end
311
end
312
313
#
314
# Prompt the user for input if possible.
315
# XXX: This is not thread-safe on Windows
316
#
317
def prompt(query)
318
if (user_output and user_input)
319
user_output.print("\n" + query)
320
user_input.sysread(2)
321
end
322
end
323
324
#
325
# Check the return value of a yes/no prompt
326
#
327
def prompt_yesno(query)
328
(prompt(query + " [y/N] ") =~ /^y/i) ? true : false
329
end
330
331
end
332
333
end
334
end
335
336
337