em-ssh 0.1.0 → 0.2.1
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 +1 -1
- data/lib/em-ssh.rb +9 -7
- data/lib/em-ssh/authentication-session.rb +20 -20
- data/lib/em-ssh/callbacks.rb +14 -15
- data/lib/em-ssh/connection.rb +264 -270
- data/lib/em-ssh/log.rb +5 -5
- data/lib/em-ssh/packet-stream.rb +8 -9
- data/lib/em-ssh/server-version.rb +6 -7
- data/lib/em-ssh/session.rb +4 -5
- data/lib/em-ssh/shell.rb +79 -67
- data/lib/em-ssh/version.rb +2 -2
- metadata +10 -10
data/README.md
CHANGED
@@ -53,7 +53,7 @@ See [http://net-ssh.github.com/ssh/v2/api/index.html](http://net-ssh.github.com/
|
|
53
53
|
|
54
54
|
##Shell
|
55
55
|
|
56
|
-
Em-ssh provides an
|
56
|
+
Em-ssh provides an expect-like shell abstraction layer on top of net-ssh in EM::Ssh::Shell
|
57
57
|
|
58
58
|
### Example
|
59
59
|
require 'em-ssh/shell'
|
data/lib/em-ssh.rb
CHANGED
@@ -15,7 +15,7 @@ module EventMachine
|
|
15
15
|
# end
|
16
16
|
class Ssh
|
17
17
|
DEFAULT_PORT = 22
|
18
|
-
|
18
|
+
|
19
19
|
# Generic error tag
|
20
20
|
module Error; end
|
21
21
|
# Any class that inherits from SshError will be an Exception and include a Ssh::Error tag
|
@@ -25,8 +25,8 @@ module EventMachine
|
|
25
25
|
class Disconnected < SshError; end
|
26
26
|
class ConnectionFailed < SshError; end
|
27
27
|
class ConnectionTimeout < ConnectionFailed; end
|
28
|
-
|
29
|
-
|
28
|
+
|
29
|
+
|
30
30
|
class << self
|
31
31
|
attr_writer :logger
|
32
32
|
# Creates a logger when necessary
|
@@ -34,7 +34,7 @@ module EventMachine
|
|
34
34
|
def logger(level = Logger::WARN)
|
35
35
|
@logger ||= ::Logger.new(STDERR).tap{ |l| l.level = level }
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
# Connect to an ssh server
|
39
39
|
# @param [String] host
|
40
40
|
# @param [String] user
|
@@ -42,7 +42,7 @@ module EventMachine
|
|
42
42
|
# @yield [Session] an EventMachine compatible Net::SSH::Session
|
43
43
|
# @see http://net-ssh.github.com/ssh/v2/api/index.html
|
44
44
|
# @return [Session]
|
45
|
-
# @example
|
45
|
+
# @example
|
46
46
|
# EM::Ssh.start(host, user, options) do |connection|
|
47
47
|
# log.debug "**** connected: #{connection}"
|
48
48
|
# connection.open_channel do |channel|
|
@@ -55,7 +55,7 @@ module EventMachine
|
|
55
55
|
end
|
56
56
|
alias :start :connect
|
57
57
|
end # << self
|
58
|
-
|
58
|
+
|
59
59
|
# Pull in the constants from Net::SSH::[Transport, Connection and Authentication]
|
60
60
|
# and define them locally.
|
61
61
|
[:Transport, :Connection, :Authentication]
|
@@ -65,10 +65,12 @@ module EventMachine
|
|
65
65
|
const_set(name, mod.const_get(name))
|
66
66
|
end # |name|
|
67
67
|
end # |module|
|
68
|
-
|
68
|
+
|
69
69
|
end # class::Ssh
|
70
70
|
end # module::EventMachine
|
71
71
|
|
72
|
+
EM::P::Ssh = EventMachine::Ssh
|
73
|
+
|
72
74
|
|
73
75
|
require 'em-ssh/callbacks'
|
74
76
|
require 'em-ssh/connection'
|
@@ -2,36 +2,36 @@ module EventMachine
|
|
2
2
|
class Ssh
|
3
3
|
class AuthenticationSession < Net::SSH::Authentication::Session
|
4
4
|
include Log
|
5
|
-
|
5
|
+
|
6
6
|
def authenticate(*args)
|
7
7
|
debug { "authenticate(#{args.join(", ")})" }
|
8
8
|
super(*args)
|
9
9
|
end # authenticate(*args)
|
10
|
-
|
10
|
+
|
11
11
|
# Returns once an acceptable auth packet is received.
|
12
12
|
def next_message
|
13
|
-
|
13
|
+
packet = transport.next_message
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
15
|
+
case packet.type
|
16
|
+
when USERAUTH_BANNER
|
17
|
+
info { packet[:message] }
|
18
|
+
transport.fire(:auth_banner, packet[:message])
|
19
|
+
return next_message
|
20
|
+
when USERAUTH_FAILURE
|
21
|
+
@allowed_auth_methods = packet[:authentications].split(/,/)
|
22
|
+
debug { "allowed methods: #{packet[:authentications]}" }
|
23
|
+
return packet
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
when USERAUTH_METHOD_RANGE, SERVICE_ACCEPT
|
26
|
+
return packet
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
when USERAUTH_SUCCESS
|
29
|
+
transport.hint :authenticated
|
30
|
+
return packet
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
else
|
33
|
+
raise SshError, "unexpected message #{packet.type} (#{packet})"
|
34
|
+
end
|
35
35
|
end # next_message
|
36
36
|
end # class::AuthenticationSession
|
37
37
|
end # module::Ssh
|
data/lib/em-ssh/callbacks.rb
CHANGED
@@ -19,13 +19,13 @@ module EventMachine
|
|
19
19
|
# end # @header[-1] == "\n"
|
20
20
|
# end # |data|
|
21
21
|
module Callbacks
|
22
|
-
|
22
|
+
|
23
23
|
# @return [Hash] The registered callbacks
|
24
24
|
def callbacks
|
25
25
|
@clbks ||= {}
|
26
26
|
end # callbacks
|
27
|
-
|
28
|
-
|
27
|
+
|
28
|
+
|
29
29
|
# Signal that an event has occured.
|
30
30
|
# Each callback will receive whatever args are passed to fire, or the object that the event was fired upon.
|
31
31
|
# @param [Symbol] event
|
@@ -36,22 +36,22 @@ module EventMachine
|
|
36
36
|
args = self if args.empty?
|
37
37
|
(callbacks[event] ||= []).clone.map { |cb| cb.call(*args) }
|
38
38
|
end # fire(event)
|
39
|
-
|
39
|
+
|
40
40
|
# Register a callback to be fired when a matching event occurs.
|
41
41
|
# The callback will be fired when the event occurs until it returns true.
|
42
42
|
# @param [Symbol] event
|
43
43
|
def on(event, &blk)
|
44
|
-
#log.debug("#{self}.on(#{event.inspect}, #{blk})")
|
44
|
+
#log.debug("#{self}.on(#{event.inspect}, #{blk})")
|
45
45
|
if block_given?
|
46
46
|
raise "event (#{event.inspect}) must be a symbol when a block is given" unless event.is_a?(Symbol)
|
47
47
|
return Callback.new(self, event, &blk).tap{|cb| (callbacks[event] ||= []).push(cb) }
|
48
48
|
end # block_given?
|
49
|
-
|
49
|
+
|
50
50
|
raise "event (#{event.inspect}) must be a Callback when a block is not given" unless event.is_a?(Callback)
|
51
51
|
(callbacks[event] ||= []).push(event)
|
52
52
|
return event
|
53
53
|
end # on(event, &blk)
|
54
|
-
|
54
|
+
|
55
55
|
# Registers a callback that will be canceled after the first time it is called.
|
56
56
|
def on_next(event, &blk)
|
57
57
|
cb = on(event) do |*args|
|
@@ -59,8 +59,8 @@ module EventMachine
|
|
59
59
|
blk.call(*args)
|
60
60
|
end # |*args|
|
61
61
|
end # on_next(event, &blk)
|
62
|
-
|
63
|
-
|
62
|
+
|
63
|
+
|
64
64
|
class Callback
|
65
65
|
# The object that keeps this callback
|
66
66
|
attr_reader :obj
|
@@ -68,32 +68,31 @@ module EventMachine
|
|
68
68
|
attr_reader :event
|
69
69
|
# The block to call when the event is fired
|
70
70
|
attr_reader :block
|
71
|
-
|
71
|
+
|
72
72
|
def initialize(obj, event, &blk)
|
73
73
|
raise ArgumentError.new("a block is required") unless block_given?
|
74
74
|
@obj = obj
|
75
75
|
@event = event
|
76
76
|
@block = blk
|
77
77
|
end # initialize(obj, event, &blk)
|
78
|
-
|
78
|
+
|
79
79
|
# Call the callback with optional arguments
|
80
80
|
def call(*args)
|
81
81
|
block.call(*args)
|
82
82
|
end # call(*args)
|
83
|
-
|
84
|
-
# Registers the callback with the object.
|
83
|
+
|
84
|
+
# Registers the callback with the object.
|
85
85
|
# This is useful if you cancel the callback at one point and want to re-enable it later on.
|
86
86
|
def register
|
87
87
|
@obj.on(self)
|
88
88
|
end # register
|
89
|
-
|
89
|
+
|
90
90
|
def cancel
|
91
91
|
raise "#{@obj} does not have any callbacks for #{@event.inspect}" unless @obj.respond_to?(:callbacks) && @obj.callbacks.respond_to?(:[]) && @obj.callbacks[@event].respond_to?(:delete)
|
92
92
|
@obj.callbacks[@event].delete(self)
|
93
93
|
self
|
94
94
|
end # cancel
|
95
95
|
end # class::Callback
|
96
|
-
|
97
96
|
end # module::Callbacks
|
98
97
|
end # class::Ssh
|
99
98
|
end # module::EventMachine
|
data/lib/em-ssh/connection.rb
CHANGED
@@ -5,283 +5,277 @@ module EventMachine
|
|
5
5
|
# itself into Net::SSH so that the EventMachine reactor loop can take the place of the Net::SSH event loop.
|
6
6
|
# Most of the methods here are only for compatibility with Net::SSH
|
7
7
|
class Connection < EventMachine::Connection
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
8
|
+
include Log
|
9
|
+
|
10
|
+
# Allows other objects to register callbacks with events that occur on a Ssh instance
|
11
|
+
include Callbacks
|
12
|
+
|
13
|
+
# maximum number of seconds to wait for a connection
|
14
|
+
TIMEOUT = 20
|
15
|
+
# @return [String] The host to connect to, as given to the constructor.
|
16
|
+
attr_reader :host
|
17
|
+
|
18
|
+
# @return [Fixnum] the port number (DEFAULT_PORT) to connect to, as given in the options to the constructor.
|
19
|
+
attr_reader :port
|
20
|
+
|
21
|
+
# @return [ServerVersion] The ServerVersion instance that encapsulates the negotiated protocol version.
|
22
|
+
attr_reader :server_version
|
23
|
+
|
24
|
+
# The Algorithms instance used to perform key exchanges.
|
25
|
+
attr_reader :algorithms
|
26
|
+
|
27
|
+
# The host-key verifier object used to verify host keys, to ensure that the connection is not being spoofed.
|
28
|
+
attr_reader :host_key_verifier
|
29
|
+
|
30
|
+
# The hash of options that were given to the object at initialization.
|
31
|
+
attr_reader :options
|
32
|
+
|
33
|
+
# @return [PacketStream] emulates a socket and ssh packetstream
|
34
|
+
attr_reader :socket
|
35
|
+
|
36
|
+
# @return [Boolean] true if the connection has been closed
|
37
|
+
def closed?
|
38
|
+
@closed == true
|
39
|
+
end
|
40
|
+
|
41
|
+
# Close the connection
|
42
|
+
def close
|
43
|
+
# #unbind will update @closed
|
44
|
+
close_connection
|
45
|
+
end
|
46
|
+
|
47
|
+
# Send a packet to the server
|
48
|
+
def send_message(message)
|
49
|
+
@socket.send_packet(message)
|
50
|
+
end
|
51
|
+
alias :enqueue_message :send_message
|
52
|
+
|
53
|
+
def next_message
|
54
|
+
return @queue.shift if @queue.any? && algorithms.allow?(@queue.first)
|
55
|
+
f = Fiber.current
|
56
|
+
cb = on(:packet) do |packet|
|
57
|
+
if @queue.any? && algorithms.allow?(@queue.first)
|
58
|
+
cb.cancel
|
59
|
+
f.resume(@queue.shift)
|
60
|
+
end
|
61
|
+
end # :packet
|
62
|
+
return Fiber.yield
|
63
|
+
end # next_message
|
64
|
+
|
65
|
+
# Returns a new service_request packet for the given service name, ready
|
66
|
+
# for sending to the server.
|
67
|
+
def service_request(service)
|
68
|
+
Net::SSH::Buffer.from(:byte, SERVICE_REQUEST, :string, service)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Requests a rekey operation, and simulates a block until the operation completes.
|
72
|
+
# If a rekey is already pending, this returns immediately, having no effect.
|
73
|
+
def rekey!
|
74
|
+
if !algorithms.pending?
|
55
75
|
f = Fiber.current
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
end
|
61
|
-
end # :packet
|
76
|
+
on_next(:algo_init) do
|
77
|
+
f.resume
|
78
|
+
end # :algo_init
|
79
|
+
algorithms.rekey!
|
62
80
|
return Fiber.yield
|
63
|
-
end # next_message
|
64
|
-
|
65
|
-
# Returns a new service_request packet for the given service name, ready
|
66
|
-
# for sending to the server.
|
67
|
-
def service_request(service)
|
68
|
-
Net::SSH::Buffer.from(:byte, SERVICE_REQUEST, :string, service)
|
69
81
|
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns immediately if a rekey is already in process. Otherwise, if a
|
85
|
+
# rekey is needed (as indicated by the socket, see PacketStream#if_needs_rekey?)
|
86
|
+
# one is performed, causing this method to block until it completes.
|
87
|
+
def rekey_as_needed
|
88
|
+
return if algorithms.pending?
|
89
|
+
socket.if_needs_rekey? { rekey! }
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
##
|
95
|
+
# EventMachine callbacks
|
96
|
+
###
|
97
|
+
def post_init
|
98
|
+
@socket = PacketStream.new(self)
|
99
|
+
@data = @socket.input
|
100
|
+
end # post_init
|
101
|
+
|
102
|
+
# @return
|
103
|
+
def unbind
|
104
|
+
debug("#{self} is unbound")
|
105
|
+
fire(:closed)
|
106
|
+
@closed = true
|
107
|
+
end
|
108
|
+
|
109
|
+
def receive_data(data)
|
110
|
+
debug("read #{data.length} bytes")
|
111
|
+
@data.append(data)
|
112
|
+
fire(:data, data)
|
113
|
+
end
|
114
|
+
|
115
|
+
def connection_completed
|
116
|
+
@contimeout.cancel
|
117
|
+
@nocon.cancel
|
118
|
+
end # connection_completed
|
119
|
+
|
120
|
+
def initialize(options = {})
|
121
|
+
debug("#{self.class}.new(#{options})")
|
122
|
+
@host = options[:host]
|
123
|
+
@port = options[:port]
|
124
|
+
@password = options[:password]
|
125
|
+
@queue = []
|
126
|
+
@options = options
|
127
|
+
@timeout = options[:timeout] || TIMEOUT
|
128
|
+
|
129
|
+
begin
|
130
|
+
on(:connected, &options[:callback]) if options[:callback]
|
131
|
+
@nocon = on(:closed) { raise ConnectionFailed, @host }
|
132
|
+
@contimeout = EM::Timer.new(@timeout) { raise ConnectionTimeout, @host }
|
133
|
+
|
134
|
+
@error_callback = lambda { |code| raise SshError.new(code) }
|
135
|
+
|
136
|
+
@host_key_verifier = select_host_key_verifier(options[:paranoid])
|
137
|
+
@server_version = ServerVersion.new(self)
|
138
|
+
on(:version_negotiated) do
|
139
|
+
@data.consume!(@server_version.header.length)
|
140
|
+
@algorithms = Net::SSH::Transport::Algorithms.new(self, options)
|
141
|
+
|
142
|
+
register_data_handler
|
70
143
|
|
71
|
-
# Requests a rekey operation, and simulates a block until the operation completes.
|
72
|
-
# If a rekey is already pending, this returns immediately, having no effect.
|
73
|
-
def rekey!
|
74
|
-
if !algorithms.pending?
|
75
|
-
f = Fiber.current
|
76
144
|
on_next(:algo_init) do
|
77
|
-
|
145
|
+
auth = AuthenticationSession.new(self, options)
|
146
|
+
user = options.fetch(:user, user)
|
147
|
+
Fiber.new do
|
148
|
+
if auth.authenticate("ssh-connection", user, options[:password])
|
149
|
+
fire(:connected, Session.new(self, options))
|
150
|
+
else
|
151
|
+
fire(:error, Net::SSH::AuthenticationFailed.new(user))
|
152
|
+
close_connection
|
153
|
+
end # auth.authenticate("ssh-connection", user, options[:password])
|
154
|
+
end.resume # Fiber
|
78
155
|
end # :algo_init
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
#
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
#
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
on_next(:algo_init) do
|
145
|
-
auth = AuthenticationSession.new(self, options)
|
146
|
-
user = options.fetch(:user, user)
|
147
|
-
Fiber.new do
|
148
|
-
if auth.authenticate("ssh-connection", user, options[:password])
|
149
|
-
fire(:connected, Session.new(self, options))
|
150
|
-
else
|
151
|
-
fire(:error, Net::SSH::AuthenticationFailed.new(user))
|
152
|
-
close_connection
|
153
|
-
end # auth.authenticate("ssh-connection", user, options[:password])
|
154
|
-
end.resume # Fiber
|
155
|
-
end # :algo_init
|
156
|
-
end # :version_negotiated
|
157
|
-
|
158
|
-
rescue Exception => e
|
159
|
-
log.fatal("caught an error during initialization: #{e}\n #{e.backtrace.join("\n ")}")
|
160
|
-
Process.exit
|
161
|
-
end # begin
|
162
|
-
self
|
163
|
-
end # initialize(options = {})
|
164
|
-
|
165
|
-
|
166
|
-
##
|
167
|
-
# Helpers required for compatibility with Net::SSH
|
168
|
-
##
|
169
|
-
|
170
|
-
# Returns the host (and possibly IP address) in a format compatible with
|
171
|
-
# SSH known-host files.
|
172
|
-
def host_as_string
|
173
|
-
@host_as_string ||= "#{host}".tap do |string|
|
174
|
-
string = "[#{string}]:#{port}" if port != DEFAULT_PORT
|
175
|
-
_, ip = Socket.unpack_sockaddr_in(get_peername)
|
176
|
-
if ip != host
|
177
|
-
string << "," << (port != DEFAULT_PORT ? "[#{ip}]:#{port}" : ip)
|
178
|
-
end # ip != host
|
179
|
-
end # |string|
|
180
|
-
end # host_as_string
|
181
|
-
|
182
|
-
alias :logger :log
|
183
|
-
|
184
|
-
|
185
|
-
# Configure's the packet stream's client state with the given set of
|
186
|
-
# options. This is typically used to define the cipher, compression, and
|
187
|
-
# hmac algorithms to use when sending packets to the server.
|
188
|
-
def configure_client(options={})
|
189
|
-
@socket.client.set(options)
|
190
|
-
end
|
191
|
-
|
192
|
-
# Configure's the packet stream's server state with the given set of
|
193
|
-
# options. This is typically used to define the cipher, compression, and
|
194
|
-
# hmac algorithms to use when reading packets from the server.
|
195
|
-
def configure_server(options={})
|
196
|
-
@socket.server.set(options)
|
197
|
-
end
|
198
|
-
|
199
|
-
# Sets a new hint for the packet stream, which the packet stream may use
|
200
|
-
# to change its behavior. (See PacketStream#hints).
|
201
|
-
def hint(which, value=true)
|
202
|
-
@socket.hints[which] = value
|
203
|
-
end
|
204
|
-
|
205
|
-
# Returns a new service_request packet for the given service name, ready
|
206
|
-
# for sending to the server.
|
207
|
-
def service_request(service)
|
208
|
-
Net::SSH::Buffer.from(:byte, SERVICE_REQUEST, :string, service)
|
209
|
-
end
|
210
|
-
|
211
|
-
# Returns a hash of information about the peer (remote) side of the socket,
|
212
|
-
# including :ip, :port, :host, and :canonized (see #host_as_string).
|
213
|
-
def peer
|
214
|
-
@peer ||= {}.tap do |p|
|
215
|
-
_, ip = Socket.unpack_sockaddr_in(get_peername)
|
216
|
-
p[:ip] = ip
|
217
|
-
p[:port] = @port.to_i
|
218
|
-
p[:host] = @host
|
219
|
-
p[:canonized] = host_as_string
|
220
|
-
end
|
156
|
+
end # :version_negotiated
|
157
|
+
|
158
|
+
rescue Exception => e
|
159
|
+
log.fatal("caught an error during initialization: #{e}\n #{e.backtrace.join("\n ")}")
|
160
|
+
Process.exit
|
161
|
+
end # begin
|
162
|
+
self
|
163
|
+
end # initialize(options = {})
|
164
|
+
|
165
|
+
|
166
|
+
##
|
167
|
+
# Helpers required for compatibility with Net::SSH
|
168
|
+
##
|
169
|
+
|
170
|
+
# Returns the host (and possibly IP address) in a format compatible with
|
171
|
+
# SSH known-host files.
|
172
|
+
def host_as_string
|
173
|
+
@host_as_string ||= "#{host}".tap do |string|
|
174
|
+
string = "[#{string}]:#{port}" if port != DEFAULT_PORT
|
175
|
+
_, ip = Socket.unpack_sockaddr_in(get_peername)
|
176
|
+
if ip != host
|
177
|
+
string << "," << (port != DEFAULT_PORT ? "[#{ip}]:#{port}" : ip)
|
178
|
+
end # ip != host
|
179
|
+
end # |string|
|
180
|
+
end # host_as_string
|
181
|
+
|
182
|
+
alias :logger :log
|
183
|
+
|
184
|
+
|
185
|
+
# Configure's the packet stream's client state with the given set of
|
186
|
+
# options. This is typically used to define the cipher, compression, and
|
187
|
+
# hmac algorithms to use when sending packets to the server.
|
188
|
+
def configure_client(options={})
|
189
|
+
@socket.client.set(options)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Configure's the packet stream's server state with the given set of
|
193
|
+
# options. This is typically used to define the cipher, compression, and
|
194
|
+
# hmac algorithms to use when reading packets from the server.
|
195
|
+
def configure_server(options={})
|
196
|
+
@socket.server.set(options)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Sets a new hint for the packet stream, which the packet stream may use
|
200
|
+
# to change its behavior. (See PacketStream#hints).
|
201
|
+
def hint(which, value=true)
|
202
|
+
@socket.hints[which] = value
|
203
|
+
end
|
204
|
+
|
205
|
+
# Returns a new service_request packet for the given service name, ready
|
206
|
+
# for sending to the server.
|
207
|
+
def service_request(service)
|
208
|
+
Net::SSH::Buffer.from(:byte, SERVICE_REQUEST, :string, service)
|
209
|
+
end
|
210
|
+
|
211
|
+
# Returns a hash of information about the peer (remote) side of the socket,
|
212
|
+
# including :ip, :port, :host, and :canonized (see #host_as_string).
|
213
|
+
def peer
|
214
|
+
@peer ||= {}.tap do |p|
|
215
|
+
_, ip = Socket.unpack_sockaddr_in(get_peername)
|
216
|
+
p[:ip] = ip
|
217
|
+
p[:port] = @port.to_i
|
218
|
+
p[:host] = @host
|
219
|
+
p[:canonized] = host_as_string
|
221
220
|
end
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
if
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
end # paranoid
|
281
|
-
end # select_host_key_verifier(paranoid)
|
282
|
-
|
221
|
+
end
|
222
|
+
|
223
|
+
private
|
224
|
+
|
225
|
+
# Register the primary :data callback
|
226
|
+
# @return [Callback] the callback that was registered
|
227
|
+
def register_data_handler
|
228
|
+
on(:data) do |data|
|
229
|
+
while (packet = @socket.poll_next_packet)
|
230
|
+
case packet.type
|
231
|
+
when DISCONNECT
|
232
|
+
close_connection
|
233
|
+
when IGNORE
|
234
|
+
debug("IGNORE packet received: #{packet[:data].inspect}")
|
235
|
+
when UNIMPLEMENTED
|
236
|
+
log.warn("UNIMPLEMENTED: #{packet[:number]}")
|
237
|
+
when DEBUG
|
238
|
+
log.send((packet[:always_display] ? :fatal : :debug), packet[:message])
|
239
|
+
when KEXINIT
|
240
|
+
Fiber.new do
|
241
|
+
begin
|
242
|
+
algorithms.accept_kexinit(packet)
|
243
|
+
fire(:algo_init) if algorithms.initialized?
|
244
|
+
rescue Exception => e
|
245
|
+
fire(:error, e)
|
246
|
+
end # begin
|
247
|
+
end.resume
|
248
|
+
else
|
249
|
+
@queue.push(packet)
|
250
|
+
if algorithms.allow?(packet)
|
251
|
+
fire(:packet, packet)
|
252
|
+
fire(:session_packet, packet) if packet.type >= GLOBAL_REQUEST
|
253
|
+
end # algorithms.allow?(packet)
|
254
|
+
socket.consume!
|
255
|
+
end # packet.type
|
256
|
+
end # (packet = @socket.poll_next_packet)
|
257
|
+
end # |data|
|
258
|
+
end # register_data_handler
|
259
|
+
|
260
|
+
# Instantiates a new host-key verification class, based on the value of
|
261
|
+
# the parameter. When true or nil, the default Lenient verifier is
|
262
|
+
# returned. If it is false, the Null verifier is returned, and if it is
|
263
|
+
# :very, the Strict verifier is returned. If the argument happens to
|
264
|
+
# respond to :verify, it is returned directly. Otherwise, an exception
|
265
|
+
# is raised.
|
266
|
+
# Taken from Net::SSH::Session
|
267
|
+
def select_host_key_verifier(paranoid)
|
268
|
+
case paranoid
|
269
|
+
when true, nil then
|
270
|
+
Net::SSH::Verifiers::Lenient.new
|
271
|
+
when false then
|
272
|
+
Net::SSH::Verifiers::Null.new
|
273
|
+
when :very then
|
274
|
+
Net::SSH::Verifiers::Strict.new
|
275
|
+
else
|
276
|
+
paranoid.respond_to?(:verify) ? paranoid : (raise ArgumentError.new("argument to :paranoid is not valid: #{paranoid.inspect}"))
|
277
|
+
end # paranoid
|
278
|
+
end # select_host_key_verifier(paranoid)
|
283
279
|
end # class::Connection < EventMachine::Connection
|
284
280
|
end # module::Ssh
|
285
281
|
end # module::EventMachine
|
286
|
-
|
287
|
-
|
data/lib/em-ssh/log.rb
CHANGED
@@ -5,23 +5,23 @@ module EventMachine
|
|
5
5
|
def log
|
6
6
|
EventMachine::Ssh.logger
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
def debug(msg = nil, &blk)
|
10
10
|
log.debug("#{self.class}".downcase.gsub("::",".") + " #{msg}", &blk)
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
def info(msg = nil, &blk)
|
14
14
|
log.info("#{self.class}".downcase.gsub("::",".") + " #{msg}", &blk)
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def fatal(msg = nil, &blk)
|
18
18
|
log.fatal("#{self.class}".downcase.gsub("::",".") + " #{msg}", &blk)
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def warn(msg = nil, &blk)
|
22
22
|
log.warn("#{self.class}".downcase.gsub("::",".") + " #{msg}", &blk)
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def error(msg = nil, &blk)
|
26
26
|
log.error("#{self.class}".downcase.gsub("::",".") + " #{msg}", &blk)
|
27
27
|
end
|
data/lib/em-ssh/packet-stream.rb
CHANGED
@@ -3,8 +3,8 @@ module EventMachine
|
|
3
3
|
class PacketStream
|
4
4
|
include Net::SSH::BufferedIo
|
5
5
|
include Log
|
6
|
-
|
7
|
-
|
6
|
+
|
7
|
+
|
8
8
|
# The map of "hints" that can be used to modify the behavior of the packet
|
9
9
|
# stream. For instance, when authentication succeeds, an "authenticated"
|
10
10
|
# hint is set, which is used to determine whether or not to compress the
|
@@ -18,12 +18,12 @@ module EventMachine
|
|
18
18
|
# The client state object, which encapsulates the algorithms used to build
|
19
19
|
# packets to send to the server.
|
20
20
|
attr_reader :client
|
21
|
-
|
21
|
+
|
22
22
|
# The input stream
|
23
23
|
attr_reader :input
|
24
24
|
# The output stream
|
25
25
|
attr_reader :output
|
26
|
-
|
26
|
+
|
27
27
|
def initialize(connection)
|
28
28
|
@connection = connection
|
29
29
|
@input = Net::SSH::Buffer.new
|
@@ -33,7 +33,7 @@ module EventMachine
|
|
33
33
|
@client = Net::SSH::Transport::State.new(self, :client)
|
34
34
|
@packet = nil
|
35
35
|
end # initialize(content="")
|
36
|
-
|
36
|
+
|
37
37
|
# Consumes n bytes from the buffer, where n is the current position
|
38
38
|
# unless otherwise specified. This is useful for removing data from the
|
39
39
|
# buffer that has previously been read, when you are expecting more data
|
@@ -44,7 +44,7 @@ module EventMachine
|
|
44
44
|
def consume!(*args)
|
45
45
|
input.consume!(*args)
|
46
46
|
end # consume!(*args)
|
47
|
-
|
47
|
+
|
48
48
|
# Tries to read the next packet. If there is insufficient data to read
|
49
49
|
# an entire packet, this returns immediately, otherwise the packet is
|
50
50
|
# read, post-processed according to the cipher, hmac, and compression
|
@@ -94,7 +94,7 @@ module EventMachine
|
|
94
94
|
|
95
95
|
return Net::SSH::Packet.new(payload)
|
96
96
|
end # poll_next_packet
|
97
|
-
|
97
|
+
|
98
98
|
# Copyright (c) 2008 Jamis Buck
|
99
99
|
def send_packet(payload)
|
100
100
|
# try to compress the packet
|
@@ -129,7 +129,7 @@ module EventMachine
|
|
129
129
|
|
130
130
|
self
|
131
131
|
end # send_packet(payload)
|
132
|
-
|
132
|
+
|
133
133
|
# Performs any pending cleanup necessary on the IO and its associated
|
134
134
|
# state objects. (See State#cleanup).
|
135
135
|
def cleanup
|
@@ -148,7 +148,6 @@ module EventMachine
|
|
148
148
|
server.reset! if server.needs_rekey?
|
149
149
|
end
|
150
150
|
end
|
151
|
-
|
152
151
|
end # class::PacketStream
|
153
152
|
end # class::Ssh
|
154
153
|
end # module::EventMachine
|
@@ -2,18 +2,18 @@ module EventMachine
|
|
2
2
|
class Ssh
|
3
3
|
class ServerVersion
|
4
4
|
include Log
|
5
|
-
|
5
|
+
|
6
6
|
attr_reader :header
|
7
7
|
attr_reader :version
|
8
|
-
|
8
|
+
|
9
9
|
def initialize(connection)
|
10
10
|
debug("#{self}.new(#{connection})")
|
11
11
|
negotiate!(connection)
|
12
12
|
end
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
17
|
def negotiate!(connection)
|
18
18
|
@version = ''
|
19
19
|
cb = connection.on(:data) do |data|
|
@@ -31,7 +31,6 @@ module EventMachine
|
|
31
31
|
end # @header[-1] == "\n"
|
32
32
|
end # |data|
|
33
33
|
end
|
34
|
-
|
35
34
|
end # class::ServerVersion
|
36
35
|
end # module::Ssh
|
37
36
|
end # module::EventMachine
|
data/lib/em-ssh/session.rb
CHANGED
@@ -2,23 +2,23 @@ module EventMachine
|
|
2
2
|
class Ssh
|
3
3
|
class Session < Net::SSH::Connection::Session
|
4
4
|
include Log
|
5
|
-
|
5
|
+
|
6
6
|
def initialize(transport, options = {})
|
7
7
|
super(transport, options)
|
8
8
|
register_callbacks
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
# Override the default, blocking behavior of Net::SSH.
|
12
12
|
# Callers to loop will still wait, but not block the loop.
|
13
13
|
def loop(wait=nil, &block)
|
14
14
|
f = Fiber.current
|
15
|
-
l = proc do
|
15
|
+
l = proc do
|
16
16
|
block.call ? EM.next_tick(&l) : f.resume
|
17
17
|
end
|
18
18
|
EM.next_tick(&l)
|
19
19
|
return Fiber.yield
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
# Override the default, blocking behavior of Net::SSH
|
23
23
|
def process(wait=nil, &block)
|
24
24
|
return true
|
@@ -44,7 +44,6 @@ module EventMachine
|
|
44
44
|
channels.each { |id, channel| channel.process unless channel.closing? }
|
45
45
|
end
|
46
46
|
end # register_callbacks
|
47
|
-
|
48
47
|
end # class::Session
|
49
48
|
end # class::Ssh
|
50
49
|
end # module::EventMachine
|
data/lib/em-ssh/shell.rb
CHANGED
@@ -22,10 +22,10 @@ module EventMachine
|
|
22
22
|
# admin_shell = shell.split
|
23
23
|
# admin_shell.on(:closed) { warn("admin shell has closed") }
|
24
24
|
# admin_shell.send_and_wait('sudo su -', ']$')
|
25
|
-
class Shell
|
25
|
+
class Shell
|
26
26
|
include Log
|
27
27
|
include Callbacks
|
28
|
-
|
28
|
+
|
29
29
|
# Global timeout for wait operations; can be overriden by :timeout option to new
|
30
30
|
TIMEOUT = 15
|
31
31
|
# @return [Net::SSH::Connection::Channel] The shell to which we can send_data
|
@@ -36,7 +36,7 @@ module EventMachine
|
|
36
36
|
attr_reader :options
|
37
37
|
# @return [Hash] the options to pass to connect automatically. They will be extracted from the opptions[:net_ssh] on initialization
|
38
38
|
attr_reader :connect_opts
|
39
|
-
|
39
|
+
|
40
40
|
# @return [String] the host to login to
|
41
41
|
attr_reader :host
|
42
42
|
# @return [String] The user to authenticate as
|
@@ -53,7 +53,7 @@ module EventMachine
|
|
53
53
|
end
|
54
54
|
# [String]
|
55
55
|
attr_writer :line_terminator
|
56
|
-
|
56
|
+
|
57
57
|
# Connect to an ssh server then start a user shell.
|
58
58
|
# @param [String] address
|
59
59
|
# @param [String] user
|
@@ -73,27 +73,31 @@ module EventMachine
|
|
73
73
|
@parent = opts[:parent]
|
74
74
|
@children = []
|
75
75
|
@reconnect = opts[:reconnect]
|
76
|
-
|
77
|
-
block_given?
|
76
|
+
|
77
|
+
if block_given?
|
78
|
+
Fiber.new { open(&blk).tap{|r| raise r if r.is_a?(Exception) } }.resume
|
79
|
+
else
|
80
|
+
open.tap{|r| raise r if r.is_a?(Exception) }
|
81
|
+
end # block_given?
|
78
82
|
end
|
79
|
-
|
83
|
+
|
80
84
|
# @return [Boolean] true if the connection should be automatically re-established; default: false
|
81
85
|
def reconnect?
|
82
86
|
@reconnect == true
|
83
|
-
end
|
84
|
-
|
87
|
+
end
|
88
|
+
|
85
89
|
# Close the connection to the server and all child shells.
|
86
90
|
# Disconnected shells cannot be split.
|
87
91
|
def disconnect
|
88
92
|
close
|
89
93
|
connection.close
|
90
94
|
end
|
91
|
-
|
95
|
+
|
92
96
|
# @return [Boolean] true if the connection is still alive
|
93
97
|
def connected?
|
94
98
|
connection && !connection.closed?
|
95
99
|
end
|
96
|
-
|
100
|
+
|
97
101
|
# Close this shell and all children.
|
98
102
|
# Even when a shell is closed it is still connected to the server.
|
99
103
|
# Fires :closed event.
|
@@ -104,170 +108,178 @@ module EventMachine
|
|
104
108
|
children.each { |c| c.close }
|
105
109
|
fire(:closed)
|
106
110
|
end
|
107
|
-
|
111
|
+
|
108
112
|
# @return [Boolean] Has this shell been closed.
|
109
113
|
def closed?
|
110
114
|
@closed == true
|
111
115
|
end
|
112
|
-
|
116
|
+
|
113
117
|
# Send a string to the server and wait for a response containing a specified String or Regex.
|
114
118
|
# @param [String] send_str
|
115
119
|
# @return [String] all data in the buffer including the wait_str if it was found
|
116
120
|
def send_and_wait(send_str, wait_str = nil, opts = {})
|
117
|
-
reconnect? ?
|
121
|
+
reconnect? ? open : raise(Disconnected) if !connected?
|
118
122
|
raise ClosedChannel if closed?
|
119
123
|
debug("send_and_wait(#{send_str.inspect}, #{wait_str.inspect}, #{opts})")
|
120
124
|
send_data(send_str)
|
121
125
|
return wait_for(wait_str, opts)
|
122
|
-
end
|
123
|
-
|
126
|
+
end
|
127
|
+
|
124
128
|
# Wait for the shell to send data containing the given string.
|
125
129
|
# @param [String, Regexp] strregex a string or regex to match the console output against.
|
126
130
|
# @param [Hash] opts
|
127
131
|
# @option opts [Fixnum] :timeout (Session::TIMEOUT) the maximum number of seconds to wait
|
128
|
-
# @return [String] the contents of the buffer
|
132
|
+
# @return [String] the contents of the buffer or a TimeoutError
|
133
|
+
# @raise Disconnected
|
134
|
+
# @raise ClosedChannel
|
135
|
+
# @raise TimeoutError
|
129
136
|
def wait_for(strregex, opts = { })
|
130
|
-
reconnect? ?
|
137
|
+
reconnect? ? open : raise(Disconnected) unless connected?
|
131
138
|
raise ClosedChannel if closed?
|
132
139
|
debug("wait_for(#{strregex.inspect}, #{opts})")
|
133
140
|
opts = { :timeout => @timeout }.merge(opts)
|
134
141
|
buffer = ''
|
135
142
|
found = nil
|
136
143
|
f = Fiber.current
|
137
|
-
|
138
|
-
timer
|
144
|
+
trace = caller
|
145
|
+
timer = nil
|
146
|
+
|
139
147
|
timeout = proc do
|
140
|
-
debug("timeout #{timer} fired")
|
141
148
|
shell.on_data {|c,d| }
|
142
|
-
|
143
|
-
|
144
|
-
rescue Exception => e
|
145
|
-
error(e)
|
146
|
-
debug(e.backtrace)
|
147
|
-
fire(:error, e)
|
148
|
-
end # begin
|
149
|
-
f.resume(nil)
|
150
|
-
shell.on_data {|c,d| }
|
151
|
-
end # timeout
|
152
|
-
|
149
|
+
f.resume(TimeoutError.new("#{host}: timeout while waiting for #{strregex.inspect}; received: #{buffer.inspect}"))
|
150
|
+
end
|
153
151
|
shell.on_data do |ch,data|
|
154
152
|
buffer = "#{buffer}#{data}"
|
155
153
|
debug("data: #{buffer.dump}")
|
156
154
|
if strregex.is_a?(Regexp) ? buffer.match(strregex) : buffer.include?(strregex)
|
157
155
|
debug("data matched")
|
158
|
-
debug("canceling timer #{timer}")
|
159
156
|
timer.respond_to?(:cancel) && timer.cancel
|
160
157
|
shell.on_data {|c,d| }
|
161
158
|
f.resume(buffer)
|
162
159
|
end
|
163
|
-
end
|
164
|
-
|
160
|
+
end
|
161
|
+
|
165
162
|
timer = EM::Timer.new(opts[:timeout], &timeout)
|
166
163
|
debug("set timer: #{timer} for #{opts[:timeout]}")
|
167
|
-
|
168
|
-
|
169
|
-
|
164
|
+
res = Fiber.yield
|
165
|
+
raise(res, res.message, Array(res.backtrace) + trace) if res.is_a?(Exception)
|
166
|
+
yield(res) if block_given?
|
167
|
+
res
|
168
|
+
end
|
169
|
+
|
170
|
+
|
170
171
|
# Open a shell on the server.
|
171
172
|
# You generally don't need to call this.
|
172
|
-
# @return [self]
|
173
|
+
# @return [self, Exception]
|
173
174
|
def open(&blk)
|
174
175
|
debug("open(#{blk})")
|
175
176
|
f = Fiber.current
|
177
|
+
trace = caller
|
176
178
|
|
177
179
|
conerr = nil
|
178
180
|
unless connected?
|
179
181
|
conerr = on(:error) do |e|
|
180
182
|
error("#{e} (#{e.class})")
|
183
|
+
e.set_backtrace(trace + Array(e.backtrace))
|
181
184
|
debug(e.backtrace)
|
182
185
|
conerr = e
|
183
186
|
f.resume(e)
|
184
187
|
end # |e|
|
185
188
|
connect
|
186
189
|
end # connected?
|
187
|
-
|
190
|
+
|
188
191
|
connection || raise(ConnectionError, "failed to create shell for #{host}: #{conerr} (#{conerr.class})")
|
189
|
-
|
192
|
+
|
190
193
|
connection.open_channel do |channel|
|
191
194
|
debug "**** channel open: #{channel}"
|
192
195
|
channel.request_pty(options[:pty] || {}) do |pty,suc|
|
193
196
|
debug "***** pty open: #{pty}; suc: #{suc}"
|
194
197
|
pty.send_channel_request("shell") do |shell,success|
|
195
|
-
|
198
|
+
unless success
|
199
|
+
f.resume(ConnectionError.new("Failed to create shell").tap{|e| e.set_backtrace(caller) })
|
200
|
+
end
|
196
201
|
conerr && conerr.cancel
|
197
202
|
debug "***** shell open: #{shell}"
|
198
|
-
@
|
203
|
+
@closed = false
|
204
|
+
@shell = shell
|
199
205
|
Fiber.new { yield(self) if block_given? }.resume
|
200
206
|
f.resume(self)
|
201
207
|
end # |shell,success|
|
202
208
|
end # |pty,suc|
|
203
209
|
end # |channel|
|
204
|
-
|
210
|
+
|
205
211
|
return Fiber.yield
|
206
|
-
end
|
207
|
-
|
212
|
+
end
|
213
|
+
|
208
214
|
# Create a new shell using the same ssh connection.
|
209
|
-
# A connection will be established if this shell is not connected.
|
215
|
+
# A connection will be established if this shell is not connected.
|
210
216
|
# If a block is provided the child will be closed after yielding.
|
211
217
|
# @yield [Shell] child
|
212
218
|
# @return [Shell] child
|
213
219
|
def split
|
214
220
|
connect unless connected?
|
215
221
|
child = self.class.new(host, user, pass, {:connection => connection, :parent => self}.merge(options))
|
216
|
-
child.line_terminator = line_terminator
|
222
|
+
child.line_terminator = line_terminator
|
217
223
|
children.push(child)
|
218
224
|
child.on(:closed) do
|
219
|
-
children.delete(child)
|
225
|
+
children.delete(child)
|
220
226
|
fire(:childless).tap{ info("fired :childless") } if children.empty?
|
221
227
|
end
|
222
228
|
fire(:split, child)
|
223
229
|
block_given? ? yield(child).tap { child.close } : child
|
224
|
-
end
|
225
|
-
|
230
|
+
end
|
231
|
+
|
226
232
|
# Connect to the server.
|
227
233
|
# Does not open the shell; use #open or #split
|
228
234
|
# You generally won't need to call this on your own.
|
229
235
|
def connect
|
230
236
|
return if connected?
|
237
|
+
trace = caller
|
231
238
|
f = Fiber.current
|
232
|
-
con = ::EM::Ssh.start(host, user, connect_opts)
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
239
|
+
con = ::EM::Ssh.start(host, user, connect_opts) { |connection| f.resume(@connection = connection) }
|
240
|
+
con.on(:error) do |e|
|
241
|
+
e.set_backtrace(trace + Array(e.backtrace))
|
242
|
+
fire(:error, e)
|
243
|
+
f.resume(e)
|
244
|
+
end
|
237
245
|
return Fiber.yield
|
238
|
-
end
|
239
|
-
|
240
|
-
|
246
|
+
end
|
247
|
+
|
248
|
+
|
241
249
|
# Send data to the ssh server shell.
|
242
250
|
# You generally don't need to call this.
|
243
251
|
# @see #send_and_wait
|
244
252
|
# @param [String] d the data to send encoded as a string
|
245
253
|
def send_data(d)
|
246
|
-
#debug("send_data: #{d.dump}#{line_terminator.dump}")
|
247
254
|
shell.send_data("#{d}#{line_terminator}")
|
248
255
|
end
|
249
|
-
|
250
|
-
|
256
|
+
|
257
|
+
|
251
258
|
def debug(msg = nil, &blk)
|
252
259
|
super("#{host} #{msg}", &blk)
|
253
260
|
end
|
254
|
-
|
261
|
+
|
255
262
|
def info(msg = nil, &blk)
|
256
263
|
super("#{host} #{msg}", &blk)
|
257
264
|
end
|
258
|
-
|
265
|
+
|
259
266
|
def fatal(msg = nil, &blk)
|
260
267
|
super("#{host} #{msg}", &blk)
|
261
268
|
end
|
262
|
-
|
269
|
+
|
263
270
|
def warn(msg = nil, &blk)
|
264
271
|
super("#{host} #{msg}", &blk)
|
265
272
|
end
|
266
|
-
|
273
|
+
|
267
274
|
def error(msg = nil, &blk)
|
268
275
|
super("#{host} #{msg}", &blk)
|
269
276
|
end
|
270
|
-
|
277
|
+
|
278
|
+
|
279
|
+
private
|
280
|
+
# TODO move private stuff down to private
|
281
|
+
# e.g., #open, #connect,
|
282
|
+
|
271
283
|
end # class::Shell
|
272
284
|
end # class::Ssh
|
273
285
|
end # module::EventMachine
|
data/lib/em-ssh/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: em-ssh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
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:
|
12
|
+
date: 2012-02-02 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: eventmachine
|
16
|
-
requirement: &
|
16
|
+
requirement: &70209705582020 !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: *
|
24
|
+
version_requirements: *70209705582020
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: net-ssh
|
27
|
-
requirement: &
|
27
|
+
requirement: &70209705581560 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70209705581560
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: ruby-termios
|
38
|
-
requirement: &
|
38
|
+
requirement: &70209705581140 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70209705581140
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: highline
|
49
|
-
requirement: &
|
49
|
+
requirement: &70209705580720 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70209705580720
|
58
58
|
description: ''
|
59
59
|
email:
|
60
60
|
- em-ssh@simulacre.org
|