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.
- 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
|