pry-remote-em 1.1.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +150 -0
- data/README.md +920 -0
- data/bin/pry-remote-em +89 -0
- data/bin/pry-remote-em-broker +28 -0
- data/lib/pry-remote-em.rb +74 -0
- data/lib/pry-remote-em/broker.rb +229 -0
- data/lib/pry-remote-em/client.rb +233 -0
- data/lib/pry-remote-em/client/broker.rb +39 -0
- data/lib/pry-remote-em/client/generic.rb +74 -0
- data/lib/pry-remote-em/client/interactive_menu.rb +193 -0
- data/lib/pry-remote-em/client/keyboard.rb +42 -0
- data/lib/pry-remote-em/client/proxy.rb +33 -0
- data/lib/pry-remote-em/metrics.rb +39 -0
- data/lib/pry-remote-em/proto.rb +148 -0
- data/lib/pry-remote-em/sandbox.rb +88 -0
- data/lib/pry-remote-em/server.rb +511 -0
- data/lib/pry-remote-em/server/shell_cmd.rb +20 -0
- data/lib/pry-remote-em/version.rb +3 -0
- metadata +121 -0
@@ -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
|