pry-remote-em 0.5.0 → 0.6.0

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.
data/README.md CHANGED
@@ -312,11 +312,65 @@ process.
312
312
 
313
313
  Message will not be displayed by the clients until the presses enter.
314
314
 
315
+ ## Authentication Event Callbacks
316
+ Available events are:
317
+
318
+ - auth_attempt - called each time authentication is attempted
319
+ - auth_fail - called each time authentication fails
320
+ - auth_ok - called each time authentication succeeds
321
+
322
+ ```ruby
323
+ log = ::Logger.new('/var/log/auth.pry.log')
324
+ obj.new.remote_pry_em('0.0.0.0', :auto, :tls => true, :auth => auth_hash) do |pry|
325
+ pry.auth_attempt do |user, ip|
326
+ log.info("got an authentication attempt for #{user} from #{ip}")
327
+ end
328
+ pry.auth_fail do |user, ip|
329
+ log.fatal("failed authentication attempt for #{user} from #{ip}")
330
+ end
331
+ pry.auth_ok do |user, ip|
332
+ log.info("successful authentication for #{user} from #{ip}")
333
+ end
334
+ end
335
+ ```
336
+
337
+ ## Shell Commands
338
+ If the pry-remote-em service is started with the ``:allow_shell_cmds =>
339
+ true`` option set it will spawn sub processes for any command prefixed
340
+ with a '.'.
341
+
342
+ ```
343
+ [1] pry(#<#<Class:0x007fe0be072618>>)> .uname -a
344
+ Darwin kiff.local 11.3.0 Darwin Kernel Version 11.3.0: Thu Jan 12 18:47:41 PST 2012; root:xnu-1699.24.23~1/RELEASE_X86_64 x86_64
345
+ ```
346
+
347
+ Interactive commands like ``vim`` will probably not behave
348
+ appropriately.
349
+
350
+
351
+ If the server was not started with the ``allow_shell_cmds`` option then
352
+ all shell commands will be met with a rejection notice.
353
+
354
+ ```
355
+ [1] pry(#<#<Class:0x007fe0be072618>>)> .ls
356
+ shell commands are not allowed by this server
357
+ ```
358
+
359
+ The server will also log whenever a user attempts to execute a shell
360
+ command.
361
+
362
+ ```
363
+ W, [2012-02-11T19:21:27.663941 #36471] WARN -- : executing shell command 'ls -al' for (127.0.0.1:63878)
364
+ ```
365
+
366
+ ```
367
+ E, [2012-02-11T19:23:40.770380 #36471] ERROR -- : refused to execute shell command 'ls' for caleb (127.0.0.1:63891)
368
+ ```
369
+
315
370
  # Missing Features
316
371
 
317
372
  - AutoDiscovery/Broker [ticket](https://github.com/simulacre/pry-remote-em/issues/11)
318
373
  - HTTP Transport [ticket](https://github.com/simulacre/pry-remote-em/issues/12)
319
- - Shell Commands [ticket](https://github.com/simulacre/pry-remote-em/issues/15)
320
374
  - Vi mode editing - RbReadline doesn't support vi edit mode. I'm looking into contributing it. PryRemoteEm uses rb-readline because the STLIB version doesn't play nice with Fibers.
321
375
  - Ssh key based authentication
322
376
 
@@ -18,8 +18,44 @@ end
18
18
 
19
19
 
20
20
  class Object
21
- def remote_pry_em(host = PryRemoteEm::DEFHOST, port = PryRemoteEm::DEFPORT, opts = {:tls => false})
21
+ def remote_pry_em(host = PryRemoteEm::DEFHOST, port = PryRemoteEm::DEFPORT, opts = {:tls => false}, &blk)
22
22
  opts = {:target => self}.merge(opts)
23
- PryRemoteEm::Server.run(opts[:target], host, port, opts)
23
+ PryRemoteEm::Server.run(opts[:target], host, port, opts, &blk)
24
24
  end
25
25
  end
26
+
27
+
28
+ unless defined?(EventMachine.popen3)
29
+ module EventMachine
30
+ # @see http://eventmachine.rubyforge.org/EventMachine.html#M000491
31
+ # @see https://gist.github.com/535644/4d5b645b96764e07ccb53539529bea9270741e1a
32
+ def self.popen3(cmd, handler=nil, *args)
33
+ klass = klass_from_handler(Connection, handler, *args)
34
+ w = Shellwords::shellwords(cmd)
35
+ w.unshift(w.first) if w.first
36
+
37
+ new_stderr = $stderr.dup
38
+ rd, wr = IO::pipe
39
+
40
+ $stderr.reopen wr
41
+ s = invoke_popen(w)
42
+ $stderr.reopen new_stderr
43
+
44
+ klass.new(s, *args).tap do |c|
45
+ EM.attach(rd, Popen3StderrHandler, c)
46
+ @conns[s] = c
47
+ yield(c) if block_given?
48
+ end
49
+ end
50
+
51
+ class Popen3StderrHandler < EventMachine::Connection
52
+ def initialize(connection)
53
+ @connection = connection
54
+ end
55
+
56
+ def receive_data(data)
57
+ @connection.receive_stderr(data)
58
+ end
59
+ end # class::Popen3StderrHandler
60
+ end # module::EventMachine
61
+ end # defined?(EventMachine.popen3)
@@ -1,5 +1,6 @@
1
1
  require 'uri'
2
2
  require 'pry-remote-em'
3
+ require 'pry-remote-em/client/keyboard'
3
4
  require 'pry/helpers/base_helpers'
4
5
  #require "readline" # doesn't work with Fiber.yield
5
6
  # - /Users/caleb/src/pry-remote-em/lib/pry-remote-em/client.rb:45:in `yield': fiber called across stack rewinding barrier (FiberError)
@@ -69,6 +70,15 @@ module PryRemoteEm
69
70
  elsif j['mb']
70
71
  Kernel.puts "\033[1m!! msg: " + j['mb'] + "\033[0m"
71
72
 
73
+ elsif j['s'] # shell command output
74
+ Kernel.puts j['s']
75
+
76
+ elsif j.include?('sc') # command completed
77
+ if @keyboard
78
+ @keyboard.bufferio(true)
79
+ @keyboard.close_connection
80
+ end
81
+
72
82
  elsif j['g'] # server banner
73
83
  Kernel.puts "[pry-remote-em] remote is #{j['g']}"
74
84
  name, version, scheme = j['g'].split(" ", 3)
@@ -133,6 +143,13 @@ module PryRemoteEm
133
143
  send_data({:b => l[2..-1]})
134
144
  elsif '!' == l[0]
135
145
  send_data({:m => l[1..-1]})
146
+ elsif '.' == l[0]
147
+ send_data({:s => l[1..-1]})
148
+ if Gem.loaded_specs["eventmachine"].version < Gem::Version.new("1.0.0.beta4")
149
+ Kernel.puts "\033[1minteractive shell commands are not well supported when running on EventMachine prior 1.0.0.beta4\033[0m"
150
+ else
151
+ @keyboard = EM.open_keyboard(Keyboard, self)
152
+ end
136
153
  else
137
154
  send_data(l)
138
155
  end # "!!" == l[0..1]
@@ -0,0 +1,46 @@
1
+ require "termios"
2
+ module PryRemoteEm
3
+ module Client
4
+ module Keyboard
5
+
6
+ def initialize(c)
7
+ @con = c
8
+ # TODO check actual current values to determine if it's enabled or not
9
+ @buff_enabled = true
10
+ # On EM < 1.0.0.beta.4 the keyboard handler and Termios don't work well together
11
+ # readline will complain that STDIN isn't a tty after Termios manipulation, so
12
+ # just don't let it happen
13
+ @manip_buff = Gem.loaded_specs["eventmachine"].version >= Gem::Version.new("1.0.0.beta.4")
14
+ bufferio(false)
15
+ # TODO retain the old SIGINT handler and reset it later
16
+ trap :SIGINT do
17
+ @con.send_data({:ssc => true})
18
+ end
19
+ end
20
+
21
+ def receive_data(d)
22
+ @con.send_data({:sd => d})
23
+ end
24
+
25
+ def unbind
26
+ bufferio(true)
27
+ trap :SIGINT do
28
+ Process.exit
29
+ end
30
+ end
31
+
32
+ # Makes stdin buffered or unbuffered.
33
+ # In unbuffered mode read and select will not wait for "\n"; also will not echo characters.
34
+ # This probably does not work on Windows.
35
+ # On EventMachine < 1.0.0.beta.4 this method doesn't do anything
36
+ def bufferio(enable)
37
+ return if !@manip_buff || (enable && @buff_enabled) || (!enable && !@buff_enabled)
38
+ attr = Termios.getattr($stdin)
39
+ enable ? (attr.c_lflag |= Termios::ICANON | Termios::ECHO) : (attr.c_lflag &= ~(Termios::ICANON|Termios::ECHO))
40
+ Termios.setattr($stdin, Termios::TCSANOW, attr)
41
+ @buff_enabled = enable
42
+ end
43
+ end # module::Keyboard
44
+ end # module::Client
45
+ end # module PryRemoteEm
46
+
@@ -1,5 +1,7 @@
1
1
  require 'pry'
2
+ require 'logger'
2
3
  require 'pry-remote-em'
4
+ require 'pry-remote-em/server/shell_cmd'
3
5
  # How it works with Pry
4
6
  #
5
7
  # When PryRemoteEm.run is called it registers with EventMachine for a given ip
@@ -34,6 +36,15 @@ module PryRemoteEm
34
36
  include JsonProto
35
37
 
36
38
  class << self
39
+ # Start a pry-remote-em server
40
+ # @param [Object] obj the object to bind pry to
41
+ # @param [String] ip the ip address to listen on
42
+ # @param [Fixnum, Symbol] port the port to listen on - if :auto the next available port will be taken
43
+ # @param [Hash] opts
44
+ # @option opts [Boolean] :tls require SSL encryption
45
+ # @option opts [Logger] :logger
46
+ # @option opts [Proc, Object] :auth require user authentication - see README
47
+ # @option opts [Boolean] :allow_shell_cmds
37
48
  def run(obj, host = DEFHOST, port = DEFPORT, opts = {:tls => false})
38
49
  tries = :auto == port ? 100.tap{ port = DEFPORT } : 1
39
50
  # TODO raise a useful exception not RuntimeError
@@ -42,6 +53,7 @@ module PryRemoteEm
42
53
  EM.start_server(host, port, PryRemoteEm::Server, obj, opts) do |pre|
43
54
  Fiber.new {
44
55
  begin
56
+ yield pre if block_given?
45
57
  Pry.start(obj, :input => pre, :output => pre)
46
58
  ensure
47
59
  pre.close_connection
@@ -58,7 +70,7 @@ module PryRemoteEm
58
70
  raise e
59
71
  end
60
72
  scheme = opts[:tls] ? 'pryems' : 'pryem'
61
- Kernel.puts "[pry-remote-em] listening for connections on #{scheme}://#{host}:#{port}/"
73
+ (opts[:logger] || ::Logger.new(STDERR)).info("[pry-remote-em] listening for connections on #{scheme}://#{host}:#{port}/")
62
74
  end # run(obj, host = DEFHOST, port = DEFPORT)
63
75
 
64
76
  # The list of pry-remote-em connections for a given object, or the list of all pry-remote-em
@@ -81,9 +93,27 @@ module PryRemoteEm
81
93
  end # class << self
82
94
 
83
95
  def initialize(obj, opts = {:tls => false})
84
- @obj = obj
85
- @after_auth = []
86
- @opts = opts
96
+ @obj = obj
97
+ @opts = opts
98
+ @allow_shell_cmds = opts[:allow_shell_cmds]
99
+ @log = opts[:logger] || ::Logger.new(STDERR)
100
+ # Blocks that will be called on each authentication attempt, prior checking the credentials
101
+ @auth_attempt_cbs = []
102
+ # All authentication attempts that occured before an auth callback was registered
103
+ @auth_attempts = []
104
+ # Blocks that will be called on each failed authentication attempt
105
+ @auth_fail_cbs = []
106
+ # All failed authentication attempts that occured before an auth callback was reigstered
107
+ @auth_fails = []
108
+ # Blocks that will be called on successful authentication attempt
109
+ @auth_ok_cbs = []
110
+ # All successful authentication attemps that occured before the auth ok callbacks were registered
111
+ @auth_oks = []
112
+
113
+ # The number maximum number of authentication attempts that are permitted
114
+ @auth_tries = 5
115
+ # Data to be sent after the user successfully authenticates if authentication is required
116
+ @after_auth = []
87
117
  return unless (a = opts[:auth])
88
118
  if a.is_a?(Proc)
89
119
  return fail("auth handler Procs must take two arguments not (#{a.arity})") unless a.arity == 2
@@ -95,28 +125,26 @@ module PryRemoteEm
95
125
  return error("auth handler objects must respond to :call, or :[]") unless a.respond_to?(:[])
96
126
  @auth = lambda {|u,p| a[u] && a[u] == p }
97
127
  end
98
- @auth_tries = 5
99
128
  end
100
129
 
101
130
  def post_init
102
131
  @lines = []
103
132
  Pry.config.pager, @old_pager = false, Pry.config.pager
104
- Pry.config.system, @old_system = PryRemoteEm::Server::System, Pry.config.system
105
133
  @auth_required = @auth
106
134
  port, ip = Socket.unpack_sockaddr_in(get_peername)
107
- Kernel.puts "[pry-remote-em] received client connection from #{ip}:#{port}"
135
+ @log.info("[pry-remote-em] received client connection from #{ip}:#{port}")
108
136
  send_data({:g => "PryRemoteEm #{VERSION} #{@opts[:tls] ? 'pryems' : 'pryem'}"})
109
137
  @opts[:tls] ? start_tls : (@auth_required && send_data({:a => false}))
110
138
  PryRemoteEm::Server.register(@obj, self)
111
139
  end
112
140
 
113
141
  def start_tls
114
- Kernel.puts "[pry-remote-em] starting TLS (#{peer_ip}:#{peer_port})"
142
+ @log.debug("[pry-remote-em] starting TLS (#{peer_ip}:#{peer_port})")
115
143
  super(@opts[:tls].is_a?(Hash) ? @opts[:tls] : {})
116
144
  end
117
145
 
118
146
  def ssl_handshake_completed
119
- Kernel.puts "[pry-remote-em] TLS connection established (#{peer_ip}:#{peer_port})"
147
+ @log.info("[pry-remote-em] TLS connection established (#{peer_ip}:#{peer_port})")
120
148
  send_data({:a => false}) if @auth_required
121
149
  end
122
150
 
@@ -150,13 +178,17 @@ module PryRemoteEm
150
178
  elsif j['a'] # authentication response
151
179
  return send_data({:a => true}) if !@auth || !@auth_required
152
180
  return send_data({:a => 'auth data must be a two element array'}) unless j['a'].is_a?(Array) && j['a'].length == 2
181
+ auth_attempt(j['a'][0], peer_ip)
153
182
  unless (@auth_required = !@auth.call(*j['a']))
183
+ @user = j['a'][0]
184
+ auth_ok(j['a'][0], peer_ip)
154
185
  authenticated!
155
186
  else
187
+ auth_fail(j['a'][0], peer_ip)
156
188
  if @auth_tries <= 0
157
189
  msg = "max authentication attempts reached"
158
190
  send_data({:a => msg})
159
- Kernel.puts "[pry-remote-em] #{msg} (#{peer_ip}:#{peer_port})"
191
+ @log.debug("[pry-remote-em] #{msg} (#{peer_ip}:#{peer_port})")
160
192
  return close_connection_after_writing
161
193
  end
162
194
  @auth_tries -= 1
@@ -171,6 +203,24 @@ module PryRemoteEm
171
203
  peers(:all).each { |peer| peer.send_bmessage(j['b']) }
172
204
  send_last_prompt
173
205
 
206
+ elsif j['s'] # shell command
207
+ # TODO confirm shell command's are allowed
208
+ unless @allow_shell_cmds
209
+ puts "\033[1mshell commands are not allowed by this server\033[0m"
210
+ @log.error("refused to execute shell command '#{j['s']}' for #{@user} (#{peer_ip}:#{peer_port})")
211
+ send_data({:sc => -1})
212
+ send_last_prompt
213
+ else
214
+ @log.warn("executing shell command '#{j['s']}' for #{@user} (#{peer_ip}:#{peer_port})")
215
+ @shell_cmd = EM.popen3(j['s'], ShellCmd, self)
216
+ end
217
+
218
+ elsif j['sd'] # shell data
219
+ @shell_cmd.send_data(j['sd'])
220
+
221
+ elsif j['ssc'] # shell ctrl-c
222
+ @shell_cmd.close_connection
223
+
174
224
  else
175
225
  warn "received unexpected data: #{j.inspect}"
176
226
  end # j['d']
@@ -185,9 +235,8 @@ module PryRemoteEm
185
235
 
186
236
  def unbind
187
237
  Pry.config.pager = @old_pager
188
- Pry.config.system = @old_system
189
238
  PryRemoteEm::Server.unregister(@obj, self)
190
- Kernel.puts "[pry-remote-em] remote session terminated (#{peer_ip}:#{peer_port})"
239
+ @log.debug("[pry-remote-em] remote session terminated (#{peer_ip}:#{peer_port})")
191
240
  end
192
241
 
193
242
  def peers(all = false)
@@ -210,6 +259,45 @@ module PryRemoteEm
210
259
  @auth_required ? @after_auth.push({:mb => msg}) : send_data({:mb => msg})
211
260
  end
212
261
 
262
+ # Callbacks for events on the server
263
+
264
+ # Registers a block to call when authentication is attempted.
265
+ # @overload auth_attempt(&blk)
266
+ # @yield [user, ip] a block to call on each authentication attempt
267
+ # @yieldparam [String] user
268
+ # @yieldparam [String] ip
269
+ def auth_attempt(*args, &blk)
270
+ block_given? ? @auth_attempt_cbs << blk : @auth_attempts.push(args)
271
+ while (auth_data = @auth_attempts.shift)
272
+ @auth_attempt_cbs.each { |cb| cb.call(*auth_data) }
273
+ end
274
+ end # auth_attempt(*args, &blk)
275
+
276
+ # Registers a block to call when authentication fails.
277
+ # @overload auth_fail(&blk)
278
+ # @yield [user, ip] a block to call after each failed authentication attempt
279
+ # @yieldparam [String] user
280
+ # @yieldparam [String] ip
281
+ def auth_fail(*args, &blk)
282
+ block_given? ? @auth_fail_cbs << blk : @auth_fails.push(args)
283
+ while (fail_data = @auth_fails.shift)
284
+ @auth_fail_cbs.each { |cb| cb.call(*fail_data) }
285
+ end
286
+ end # auth_fail(*args, &blk)
287
+
288
+ # Registers a block to call when authentication succeeds.
289
+ # @overload auth_ok(&blk)
290
+ # @yield [user, ip] a block to call after each successful authentication attempt
291
+ # @yieldparam [String] user
292
+ # @yieldparam [String] ip
293
+ def auth_ok(*args, &blk)
294
+ block_given? ? @auth_ok_cbs << blk : @auth_oks.push(args)
295
+ while (ok_data = @auth_oks.shift)
296
+ @auth_ok_cbs.each { |cb| cb.call(*ok_data) }
297
+ end
298
+ end # auth_fail(*args, &blk)
299
+
300
+
213
301
  # Methods that make Server compatible with Pry
214
302
 
215
303
  def readline(prompt)
@@ -241,9 +329,5 @@ module PryRemoteEm
241
329
  def flush
242
330
  true
243
331
  end
244
-
245
- System = proc do |output, cmd, _|
246
- output.puts("shell commands are not yet supported")
247
- end
248
332
  end # module::Server
249
333
  end # module::PryRemoteEm
@@ -0,0 +1,20 @@
1
+ module PryRemoteEm
2
+ module Server
3
+ module ShellCmd
4
+ def initialize(pryem)
5
+ @pryem = pryem
6
+ end
7
+
8
+ def receive_data(d)
9
+ @pryem.send_data({:s => d.force_encoding("utf-8") })
10
+ end
11
+
12
+ alias :receive_stderr :receive_data
13
+
14
+ def unbind
15
+ @pryem.send_data({:sc => get_status.exitstatus })
16
+ @pryem.send_last_prompt
17
+ end
18
+ end # module::ShellCmd
19
+ end # module::Server
20
+ end # module::PryRemoteEm
@@ -1,3 +1,3 @@
1
1
  module PryRemoteEm
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pry-remote-em
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-06 00:00:00.000000000Z
12
+ date: 2012-02-11 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: eventmachine
16
- requirement: &70256102479660 !ruby/object:Gem::Requirement
16
+ requirement: &70351121077580 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70256102479660
24
+ version_requirements: *70351121077580
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: pry
27
- requirement: &70256102473680 !ruby/object:Gem::Requirement
27
+ requirement: &70351121077080 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 0.9.6
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70256102473680
35
+ version_requirements: *70351121077080
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rb-readline
38
- requirement: &70256102473100 !ruby/object:Gem::Requirement
38
+ requirement: &70351121076660 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,21 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70256102473100
46
+ version_requirements: *70351121076660
47
+ - !ruby/object:Gem::Dependency
48
+ name: ruby-termios
49
+ requirement: &70351121076120 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.9.6
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70351121076120
47
58
  - !ruby/object:Gem::Dependency
48
59
  name: highline
49
- requirement: &70256102471840 !ruby/object:Gem::Requirement
60
+ requirement: &70351121109480 !ruby/object:Gem::Requirement
50
61
  none: false
51
62
  requirements:
52
63
  - - ! '>='
@@ -54,7 +65,7 @@ dependencies:
54
65
  version: '0'
55
66
  type: :runtime
56
67
  prerelease: false
57
- version_requirements: *70256102471840
68
+ version_requirements: *70351121109480
58
69
  description: Connect to Pry remotely using EventMachine with tab-completion, paging,
59
70
  user auth and SSL
60
71
  email: pry-remote-em@simulacre.org
@@ -63,8 +74,10 @@ executables:
63
74
  extensions: []
64
75
  extra_rdoc_files: []
65
76
  files:
77
+ - lib/pry-remote-em/client/keyboard.rb
66
78
  - lib/pry-remote-em/client.rb
67
79
  - lib/pry-remote-em/json-proto.rb
80
+ - lib/pry-remote-em/server/shell_cmd.rb
68
81
  - lib/pry-remote-em/server.rb
69
82
  - lib/pry-remote-em/version.rb
70
83
  - lib/pry-remote-em.rb
@@ -95,4 +108,3 @@ signing_key:
95
108
  specification_version: 3
96
109
  summary: Connect to Pry remotely using EventMachine
97
110
  test_files: []
98
- has_rdoc: