pry-remote-em 1.1.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,88 @@
1
+ require 'pry-remote-em/metrics'
2
+
3
+ module PryRemoteEm
4
+ # See Readme for Sandbox using guide
5
+ class Sandbox
6
+ attr_accessor :pry, :server
7
+
8
+ # Use output methods as expected
9
+
10
+ %w[puts putc print p pp].each do |method|
11
+ define_method method do |*arguments|
12
+ pry.output.puts(*arguments)
13
+ end
14
+ end
15
+
16
+ # Working with errors
17
+
18
+ @@last_errors = []
19
+ @@ignore_errors = []
20
+ @@error_classes = Hash.new { |hash, key| hash[key] = 0 }
21
+
22
+ def error_classes
23
+ return puts 'No errors, yay!' if @@error_classes.empty?
24
+ puts @@error_classes.map { |key, value| "#{key}: #{value}" }
25
+ end
26
+
27
+ def error_history
28
+ return puts 'No errors, yay!' unless any_errors?
29
+ puts @@last_errors.map { |error| "#{error.source_timestamp} #{"#{error.class}: #{error.message}".sub(/(?<=^.{51}).{4,}$/, '...')}" }
30
+ end
31
+
32
+ def self.add_error(exception, source_binding = nil)
33
+ unless exception.kind_of?(Exception) && exception.backtrace && (source_binding.nil? || source_binding.kind_of?(Binding))
34
+ raise ArgumentError, 'exception with backtrace and optional binding expected'
35
+ end
36
+
37
+ return if @@last_errors.map(&:object_id).include?(exception.object_id) || @@ignore_errors.include?(exception.class)
38
+
39
+ timestamp = Time.now
40
+ exception.define_singleton_method(:source_timestamp) { timestamp }
41
+
42
+ exception.define_singleton_method(:source_binding) { source_binding } if source_binding
43
+
44
+ @@last_errors.push(exception)
45
+ @@error_classes[exception.class] += 1
46
+ Metrics.add(:errors)
47
+
48
+ maximum_errors = ENV['PRYEMSANDBOXERRORS'].nil? || ENV['PRYEMSANDBOXERRORS'].empty? ? MAXIMUM_ERRORS_IN_SANDBOX : ENV['PRYEMSANDBOXERRORS'].to_i
49
+ @@last_errors.shift if @@last_errors.size > maximum_errors
50
+ end
51
+
52
+ def self.last_errors
53
+ @@last_errors
54
+ end
55
+
56
+ def self.any_errors?
57
+ @@last_errors.any?
58
+ end
59
+
60
+ def self.last_error
61
+ @@last_errors.last
62
+ end
63
+
64
+ def self.ignore_errors
65
+ @@ignore_errors
66
+ end
67
+
68
+ %w[any_errors? last_errors last_error ignore_errors].each do |method|
69
+ define_method(method) do |*arguments|
70
+ self.class.send(method, *arguments)
71
+ end
72
+ end
73
+
74
+ # Metrics related methods
75
+
76
+ def show_metrics
77
+ puts Metrics.list.map { |key, value| "#{key}: #{value}" }
78
+ end
79
+
80
+ # Safely show in Pry prompt
81
+
82
+ def inspect
83
+ 'sandbox'
84
+ end
85
+
86
+ Pry.config.prompt_safe_objects.push(self)
87
+ end
88
+ end
@@ -0,0 +1,511 @@
1
+ require 'pry'
2
+ require 'socket'
3
+ require 'logger'
4
+ require 'securerandom'
5
+ require 'pry-remote-em'
6
+ require 'pry-remote-em/broker'
7
+ require 'pry-remote-em/server/shell_cmd'
8
+ require 'pry-remote-em/sandbox'
9
+
10
+ # How it works with Pry
11
+ #
12
+ # When PryRemoteEm.run is called it registers with EventMachine for a given ip
13
+ # and port. When a connection is received EM yields an instance of PryRemoteEm,
14
+ # we start a Fiber (f1) then call Pry.start specifying the Server instance as the
15
+ # input and output object for Pry. The Pry instance that is created goes into
16
+ # its REPL. When it gets to the read part it calls @input.readline
17
+ # (PryRemoteEm#readline) and passes a prompt, e.g., [1] pry(#<Foo>)>.
18
+ #
19
+ # PryRemoteEm#readline calls #send_data with the prompt then yields from the
20
+ # current Fiber (f1); the one we started when EventMachine yielded to us. The root
21
+ # Fiber is now active again. At some point, the root Fiber receives data from
22
+ # the client. It calls #receive_data in our Server instance. That instance then
23
+ # resumes the Fiber (f1) that was waiting for #readline to finish.
24
+ #
25
+ # Inside the resumed Fiber (f1) PryRemoteEm#readline returns the recieved data
26
+ # to the instance of Pry, which continues its REPL. Pry calls #puts, or #print
27
+ # or #write on its output object (PryRemoteEm). We send that data out to the client
28
+ # and immediately return. Pry then calls PryRemoteEm#readline again and again
29
+ # we send the prompt then immediately yield back to the root Fiber.
30
+ #
31
+ # Pry just interacts with PryRemoteEm as if it were any other blocking Readline
32
+ # object. The important bit is making sure that it is started in a new Fiber that
33
+ # can be paused and resumed as needed. PryRemoteEm#readline pauses it and
34
+ # PryRemoteEm#receive_raw resumes it.
35
+ #
36
+ # Reference:
37
+ # http://www.igvita.com/2010/03/22/untangling-evented-code-with-ruby-fibers/
38
+ module PryRemoteEm
39
+ class << self
40
+ # Local PryRemoteEm servers, including EM signatures, indexed by id. Each
41
+ # signature can be used with high level EM methods like EM.stop_server or
42
+ # EM.get_sockname. If a server has been stopped EM.get_sockname will return
43
+ # nil for that server's signature.
44
+ def servers
45
+ @servers ||= {}
46
+ end
47
+
48
+ # Safely stop one or more PryRemoteEm servers and remove them from the list
49
+ # of servers.
50
+ # @param [String, nil] argument id, url or name, use `nil` to stop them all
51
+ # @return [Hash] stopped servers if they were
52
+ def stop_server(argument = nil)
53
+ servers_to_stop = if argument
54
+ servers.select do |id, description|
55
+ argument == id || description[:urls].include?(argument) || argument == description[:name]
56
+ end
57
+ else
58
+ servers
59
+ end
60
+
61
+ servers_to_stop.each do |id, description|
62
+ EM.stop_server(description[:server]) if EM.get_sockname(description[:server])
63
+ Broker.unregister(id)
64
+ servers.delete(id)
65
+ end
66
+ end
67
+ end
68
+
69
+ module Server
70
+ include Proto
71
+
72
+ class << self
73
+ # Start a pry-remote-em server
74
+ # @param [Hash] options
75
+ # @option options [Object] :target Object to bind Pry session, default - PryRemoteEm::Sandbox instance
76
+ # @option options [String] :host The IP-address to listen on, default - 127.0.0.1 (same as PRYEMHOST environment variable)
77
+ # @option options [Fixnum, String, Symbol] :port The port to listen on - if :auto or 'auto' the next available port will be taken, default - 6463 (same as PRYEMPORT environment variable)
78
+ # @option options [String] :id Server's UUID, will be generated automatically unless you pass it explicitly
79
+ # @option options [Boolean] :tls require SSL encryption, default - false
80
+ # @option options [Logger] :logger Logger for Pry Server, default - STDERR
81
+ # @option options [Proc, Object] :auth require user authentication - see README
82
+ # @option options [Boolean] :allow_shell_cmds Allow shell commands or not, default - true
83
+ # @option options [Integer, Symbol] :port_fail set to :auto to search for available port in range from given port to port + 100, or pass explicit integer to use instaed of 100, default - 1
84
+ # @option options [String] :name Server name to show in broker list, default - object's inspect (same as PRYEMNAME environment variable)
85
+ # @option options [String] :external_url External URL to connect behind firewall, NAT, Docket etc. in form "pryem://my.host:6463", default - use given host and port and expand it to all interfaces in case of 0.0.0.0 (same as PRYEMURL environment variable)
86
+ # @option options [Integer] :heartbeat_interval Interval to send heartbeats and updated details to broker, default - 15 (same as PRYEMHBSEND environment variable)
87
+ # @option options [Boolean] :remote_broker Connect to remote broker instead of starting local one, default - false (same as PRYEMREMOTEBROKER environment variable)
88
+ # @option options [String] :broker_host Broker host to connect to, default - localhost
89
+ # @option options [String] :broker_port Broker port to connect to, default - 6462
90
+ # @option options [Hash] :details Optional details to pass to broker and show in table (should consist of string/symbol keys and simple serializable values)
91
+ def run(options = {}, &block)
92
+ description = options.dup
93
+ description[:target] ||= PryRemoteEm::Sandbox.new
94
+ description[:host] ||= ENV['PRYEMHOST'].nil? || ENV['PRYEMHOST'].empty? ? DEFAULT_SERVER_HOST : ENV['PRYEMHOST']
95
+ determine_port_and_tries(description)
96
+ determine_name(description)
97
+ description[:id] ||= SecureRandom.uuid
98
+ description[:logger] ||= ::Logger.new(STDERR)
99
+ description[:external_url] ||= ENV['PRYEMURL'] || "#{description[:tls] ? 'pryems' : 'pryem'}://#{description[:host]}:#{description[:port]}/"
100
+ description[:details] ||= {}
101
+ description[:allow_shell_cmds] = true if description[:allow_shell_cmds].nil?
102
+ description[:heartbeat_interval] ||= ENV['PRYEMHBSEND'].nil? || ENV['PRYEMHBSEND'].empty? ? HEARTBEAT_SEND_INTERVAL : ENV['PRYEMHBSEND']
103
+ description[:urls] = expand_url(description[:external_url])
104
+ description[:server] = start_server(description, &block)
105
+ description[:logger].info("[pry-remote-em] listening for connections on #{description[:external_url]}")
106
+ PryRemoteEm.servers[description[:id]] = description
107
+ register_in_broker(description)
108
+ return description
109
+ end
110
+
111
+ # The list of pry-remote-em connections for a given object, or the list of all pry-remote-em
112
+ # connections for this process.
113
+ # The peer list is used when broadcasting messages between connections.
114
+ def peers(obj = nil)
115
+ @peers ||= {}
116
+ obj.nil? ? @peers.values.flatten : (@peers[obj] ||= [])
117
+ end
118
+
119
+ # Record the association between a given object and a given pry-remote-em connection.
120
+ def register(obj, peer)
121
+ peers(obj).tap { |plist| plist.include?(peer) || plist.push(peer) }
122
+ end
123
+
124
+ # Remove the association between a given object and a given pry-remote-em connection.
125
+ def unregister(obj, peer)
126
+ peers(obj).tap {|plist| true while plist.delete(peer) }
127
+ end
128
+
129
+ private
130
+
131
+ def determine_port_and_tries(description)
132
+ description[:port] ||= ENV['PRYEMPORT'].nil? || ENV['PRYEMPORT'].empty? ? DEFAULT_SERVER_PORT : ENV['PRYEMPORT']
133
+ description[:port] = :auto if description[:port] == 'auto'
134
+ description[:port] = description[:port].to_i if description[:port].kind_of?(String)
135
+ description[:tries] = [description[:port], description[:port_fail]].include?(:auto) ? 100 : description[:port_fail] || 1
136
+ description[:port] = DEFAULT_SERVER_PORT if description[:port] == :auto
137
+ # TODO raise a useful exception not RuntimeError
138
+ raise "root permission required for port below 1024 (#{port})" if description[:port] < 1024 && Process.euid != 0
139
+ end
140
+
141
+ def determine_name(description)
142
+ description[:name] ||= ENV['PRYEMNAME']
143
+ if description[:name].nil?
144
+ object = description[:target]
145
+ inner_object = object.kind_of?(Binding) ? object.send(:eval, 'self') : object
146
+ description[:name] = Pry.view_clip(inner_object)
147
+ else
148
+ description[:custom_name] = true
149
+ end
150
+ description[:name].sub!(/(?<=^.{57}).{4,}$/, '...')
151
+ end
152
+
153
+ def expand_url(url)
154
+ return Array(url) if (uri = URI.parse(url)).host != '0.0.0.0'
155
+ Socket.ip_address_list.select(&:ipv4?).map(&:ip_address).map do |ip|
156
+ uri.clone.tap { |uri_copy| uri_copy.host = ip }.to_s
157
+ end
158
+ end
159
+
160
+ def start_server(description, &block)
161
+ EM.start_server(description[:host], description[:port], PryRemoteEm::Server, description) do |pre|
162
+ Fiber.new do
163
+ begin
164
+ yield pre if block_given?
165
+ Pry.hooks.add_hook :when_started, pre do |target, options, pry|
166
+ pry.pager = false
167
+ pry.config.prompt_name = description[:name] + ' ' if description[:custom_name]
168
+ if description[:target].is_a? PryRemoteEm::Sandbox
169
+ description[:target].pry = pry
170
+ description[:target].server = description
171
+ pry.last_exception = description[:target].last_error if description[:target].any_errors?
172
+ end
173
+ description[:pry] = pry
174
+ end
175
+ Pry.start(description[:target], input: pre, output: pre)
176
+ ensure
177
+ pre.close_connection
178
+ Pry.hooks.delete_hook :when_started, pre
179
+ end
180
+ end.resume
181
+ end
182
+ rescue => error
183
+ if error.message.include?('port is in use') && description[:tries] > 1
184
+ description[:tries] -= 1
185
+ description[:port] += 1
186
+ retry
187
+ end
188
+ raise "can't bind to #{description[:host]}:#{description[:port]} - #{error}"
189
+ end
190
+
191
+ def register_in_broker(description)
192
+ broker_description = {
193
+ id: description[:id],
194
+ urls: description[:urls],
195
+ name: description[:name],
196
+ details: description[:details],
197
+ metrics: PryRemoteEm::Metrics.list
198
+ }
199
+ broker_options = {
200
+ tls: description[:tls],
201
+ remote_broker: description[:remote_broker],
202
+ logger: description[:logger]
203
+ }
204
+ Broker.run(description[:broker_host], description[:broker_port], broker_options) do |broker|
205
+ broker.register(broker_description)
206
+
207
+ rereg = EM::PeriodicTimer.new(description[:heartbeat_interval]) do
208
+ EM.get_sockname(description[:server]) ? broker.register(broker_description) : nil #rereg.cancel
209
+ end
210
+ end
211
+ end
212
+ end # class << self
213
+
214
+ def initialize(opts = {})
215
+ @obj = opts[:target]
216
+ @opts = opts
217
+ @allow_shell_cmds = opts[:allow_shell_cmds]
218
+ @log = opts[:logger] || ::Logger.new(STDERR)
219
+ # Blocks that will be called on each authentication attempt, prior checking the credentials
220
+ @auth_attempt_cbs = []
221
+ # All authentication attempts that occured before an auth callback was registered
222
+ @auth_attempts = []
223
+ # Blocks that will be called on each failed authentication attempt
224
+ @auth_fail_cbs = []
225
+ # All failed authentication attempts that occured before an auth callback was reigstered
226
+ @auth_fails = []
227
+ # Blocks that will be called on successful authentication attempt
228
+ @auth_ok_cbs = []
229
+ # All successful authentication attemps that occured before the auth ok callbacks were registered
230
+ @auth_oks = []
231
+
232
+ # The number maximum number of authentication attempts that are permitted
233
+ @auth_tries = 5
234
+ # Data to be sent after the user successfully authenticates if authentication is required
235
+ @after_auth = []
236
+ return unless (a = opts[:auth])
237
+ if a.is_a?(Proc)
238
+ return fail("auth handler Procs must take two arguments not (#{a.arity})") unless a.arity == 2
239
+ @auth = a
240
+ elsif a.respond_to?(:call)
241
+ return fail("auth handler must take two arguments not (#{a.method(:call).arity})") unless a.method(:call).arity == 2
242
+ @auth = a
243
+ else
244
+ return error('auth handler objects must respond to :call, or :[]') unless a.respond_to?(:[])
245
+ @auth = lambda {|u,p| a[u] && a[u] == p }
246
+ end
247
+ end
248
+
249
+ def url
250
+ port, host = Socket.unpack_sockaddr_in(get_sockname)
251
+ "#{@opts[:tls] ? 'pryems' : 'pryem'}://#{host}:#{port}/"
252
+ end
253
+
254
+ def post_init
255
+ @lines = []
256
+ @auth_required = @auth
257
+ @port, @ip = Socket.unpack_sockaddr_in(get_peername)
258
+ @log.info("[pry-remote-em] received client connection from #{@ip}:#{@port}")
259
+ # TODO include first level prompt in banner
260
+ send_banner("PryRemoteEm #{VERSION} #{@opts[:tls] ? 'pryems' : 'pryem'}")
261
+ need_new_line = false
262
+ if @opts[:details].any?
263
+ send_raw("\nServer details:\n#{@opts[:details].map { |key, value| " #{key}: #{value}" } * "\n"}\n")
264
+ need_new_line = true
265
+ end
266
+ if PryRemoteEm::Metrics.any?
267
+ send_raw("\nServer metrics:\n#{PryRemoteEm::Metrics.list.map { |key, value| " #{key}: #{value}" } * "\n"}\n")
268
+ need_new_line = true
269
+ end
270
+ send_raw("\n") if need_new_line
271
+ @log.info("#{url} PryRemoteEm #{VERSION} #{@opts[:tls] ? 'pryems' : 'pryem'}")
272
+ @opts[:tls] ? start_tls : (@auth_required && send_auth(false))
273
+ PryRemoteEm::Server.register(@obj, self)
274
+ end
275
+
276
+ def start_tls
277
+ @log.debug("[pry-remote-em] starting TLS (#{peer_ip}:#{peer_port})")
278
+ super(@opts[:tls].is_a?(Hash) ? @opts[:tls] : {})
279
+ end
280
+
281
+ def ssl_handshake_completed
282
+ @log.info("[pry-remote-em] TLS connection established (#{peer_ip}:#{peer_port})")
283
+ send_auth(false) if @auth_required
284
+ end
285
+
286
+ def peer_ip
287
+ return @peer_ip if @peer_ip
288
+ return '' if get_peername.nil?
289
+ @peer_port, @peer_ip = Socket.unpack_sockaddr_in(get_peername)
290
+ @peer_ip
291
+ end
292
+
293
+ def peer_port
294
+ return @peer_port if @peer_port
295
+ return '' if get_peername.nil?
296
+ @peer_port, @peer_ip = Socket.unpack_sockaddr_in(get_peername)
297
+ @peer_port
298
+ end
299
+
300
+ def receive_clear_buffer
301
+ @opts[:pry].eval_string.replace('')
302
+ @last_prompt = @opts[:pry].select_prompt
303
+ send_last_prompt
304
+ end
305
+
306
+ def receive_raw(d)
307
+ return if require_auth
308
+
309
+ return send_last_prompt if d.nil?
310
+
311
+ if d.empty?
312
+ @lines.push('')
313
+ else
314
+ lines = d.split("\n")
315
+ @lines.push(*lines)
316
+ end
317
+
318
+ if @waiting
319
+ f, @waiting = @waiting, nil
320
+ f.resume(@lines.shift)
321
+ end
322
+ end
323
+
324
+ # tab completion request
325
+ def receive_completion(c)
326
+ return if require_auth
327
+ send_completion(@compl_proc.call(c))
328
+ end
329
+
330
+ def receive_auth(user, pass)
331
+ return send_auth(true) if !@auth || !@auth_required
332
+ return send_auth('auth data must include a user and pass') if user.nil? || pass.nil?
333
+ auth_attempt(user, peer_ip)
334
+ unless (@auth_required = !@auth.call(user, pass))
335
+ @user = user
336
+ auth_ok(user, peer_ip)
337
+ authenticated!
338
+ else
339
+ auth_fail(user, peer_ip)
340
+ if @auth_tries <= 0
341
+ msg = 'max authentication attempts reached'
342
+ send_auth(msg)
343
+ @log.debug("[pry-remote-em] #{msg} (#{peer_ip}:#{peer_port})")
344
+ return close_connection_after_writing
345
+ end
346
+ @auth_tries -= 1
347
+ end
348
+ return send_auth(!@auth_required)
349
+ end
350
+
351
+ def receive_msg(m)
352
+ return if require_auth
353
+ peers.each { |peer| peer.send_message(m, @user) }
354
+ send_last_prompt
355
+ end
356
+
357
+ def receive_msg_bcast(mb)
358
+ return if require_auth
359
+ peers(:all).each { |peer| peer.send_bmessage(mb, @user) }
360
+ send_last_prompt
361
+ end
362
+
363
+ def receive_shell_cmd(cmd)
364
+ return if require_auth
365
+ unless @allow_shell_cmds
366
+ puts "\033[1mshell commands are not allowed by this server\033[0m"
367
+ @log.error("refused to execute shell command '#{cmd}' for #{@user} (#{peer_ip}:#{peer_port})")
368
+ send_shell_result(-1)
369
+ send_last_prompt
370
+ else
371
+ @log.warn("executing shell command '#{cmd}' for #{@user} (#{peer_ip}:#{peer_port})")
372
+ @shell_cmd = EM.popen3(cmd, ShellCmd, self)
373
+ end
374
+ end
375
+
376
+ def receive_shell_data(d)
377
+ return if require_auth
378
+ @shell_cmd.send_data(d)
379
+ end
380
+
381
+ def receive_shell_sig(type)
382
+ return if require_auth
383
+ @shell_cmd.close_connection if type == :int
384
+ end
385
+
386
+ def receive_unknown(j)
387
+ return if require_auth
388
+ warn "received unexpected data: #{j.inspect}"
389
+ send_error("received unexpected data: #{j.inspect}")
390
+ send_last_prompt
391
+ end
392
+
393
+ def require_auth
394
+ return false if !@auth_required
395
+ send_auth(false)
396
+ true
397
+ end
398
+
399
+ def authenticated!
400
+ while (aa = @after_auth.shift)
401
+ aa.call
402
+ end
403
+ end
404
+
405
+ def unbind
406
+ PryRemoteEm::Server.unregister(@obj, self)
407
+ @log.debug("[pry-remote-em] remote session terminated (#{@ip}:#{@port})")
408
+ end
409
+
410
+ def peers(all = false)
411
+ plist = (all ? PryRemoteEm::Server.peers : PryRemoteEm::Server.peers(@obj)).clone
412
+ plist.delete(self)
413
+ plist
414
+ end
415
+
416
+ def send_last_prompt
417
+ @auth_required ? (after_auth { send_prompt(@last_prompt) }) : send_prompt(@last_prompt)
418
+ end
419
+
420
+ def after_auth(&blk)
421
+ # TODO perhaps replace with #auth_ok
422
+ @after_auth.push(blk)
423
+ end
424
+
425
+ # Sends a chat message to the client.
426
+ def send_message(msg, from = nil)
427
+ msg = "#{msg} (@#{from})" unless from.nil?
428
+ @auth_required ? (after_auth {send_msg(msg)}) : send_msg(msg)
429
+ end
430
+ #
431
+ # Sends a chat message to the client.
432
+ def send_bmessage(msg, from = nil)
433
+ msg = "#{msg} (@#{from})" unless from.nil?
434
+ @auth_required ? (after_auth {send_msg_bcast(msg)}) : send_msg_bcast(msg)
435
+ end
436
+
437
+ # Callbacks for events on the server
438
+
439
+ # Registers a block to call when authentication is attempted.
440
+ # @overload auth_attempt(&blk)
441
+ # @yield [user, ip] a block to call on each authentication attempt
442
+ # @yieldparam [String] user
443
+ # @yieldparam [String] ip
444
+ def auth_attempt(*args, &blk)
445
+ block_given? ? @auth_attempt_cbs << blk : @auth_attempts.push(args)
446
+ while (auth_data = @auth_attempts.shift)
447
+ @auth_attempt_cbs.each { |cb| cb.call(*auth_data) }
448
+ end
449
+ end # auth_attempt(*args, &blk)
450
+
451
+ # Registers a block to call when authentication fails.
452
+ # @overload auth_fail(&blk)
453
+ # @yield [user, ip] a block to call after each failed authentication attempt
454
+ # @yieldparam [String] user
455
+ # @yieldparam [String] ip
456
+ def auth_fail(*args, &blk)
457
+ block_given? ? @auth_fail_cbs << blk : @auth_fails.push(args)
458
+ while (fail_data = @auth_fails.shift)
459
+ @auth_fail_cbs.each { |cb| cb.call(*fail_data) }
460
+ end
461
+ end # auth_fail(*args, &blk)
462
+
463
+ # Registers a block to call when authentication succeeds.
464
+ # @overload auth_ok(&blk)
465
+ # @yield [user, ip] a block to call after each successful authentication attempt
466
+ # @yieldparam [String] user
467
+ # @yieldparam [String] ip
468
+ def auth_ok(*args, &blk)
469
+ block_given? ? @auth_ok_cbs << blk : @auth_oks.push(args)
470
+ while (ok_data = @auth_oks.shift)
471
+ @auth_ok_cbs.each { |cb| cb.call(*ok_data) }
472
+ end
473
+ end # auth_fail(*args, &blk)
474
+
475
+ def send_error(msg)
476
+ puts "\033[31m#{msg}\033[0m"
477
+ end
478
+
479
+ # Methods that make Server compatible with Pry
480
+
481
+ def readline(prompt)
482
+ @last_prompt = prompt
483
+ @auth_required ? (after_auth { send_prompt(prompt) }) : send_prompt(prompt)
484
+ return @lines.shift unless @lines.empty?
485
+ @waiting = Fiber.current
486
+ return Fiber.yield
487
+ end
488
+
489
+ def print(val)
490
+ @auth_required ? (after_auth { send_raw(val) }) : send_raw(val)
491
+ end
492
+ alias :write :print
493
+
494
+ def puts(data = '')
495
+ s = data.to_s
496
+ print(s[0] == "\n" ? s : s + "\n")
497
+ end
498
+
499
+ def completion_proc=(compl)
500
+ @compl_proc = compl
501
+ end
502
+
503
+ def tty?
504
+ true # might be a very bad idea ....
505
+ end
506
+
507
+ def flush
508
+ true
509
+ end
510
+ end # module::Server
511
+ end # module::PryRemoteEm