net-ssh-backports 6.3.0.backports
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/.github/workflows/ci.yml +93 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +21 -0
- data/.rubocop_todo.yml +1074 -0
- data/.travis.yml +51 -0
- data/CHANGES.txt +698 -0
- data/Gemfile +13 -0
- data/Gemfile.noed25519 +12 -0
- data/ISSUE_TEMPLATE.md +30 -0
- data/LICENSE.txt +19 -0
- data/Manifest +132 -0
- data/README.md +287 -0
- data/Rakefile +105 -0
- data/THANKS.txt +110 -0
- data/appveyor.yml +58 -0
- data/lib/net/ssh/authentication/agent.rb +284 -0
- data/lib/net/ssh/authentication/certificate.rb +183 -0
- data/lib/net/ssh/authentication/constants.rb +20 -0
- data/lib/net/ssh/authentication/ed25519.rb +185 -0
- data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
- data/lib/net/ssh/authentication/key_manager.rb +297 -0
- data/lib/net/ssh/authentication/methods/abstract.rb +69 -0
- data/lib/net/ssh/authentication/methods/hostbased.rb +72 -0
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +77 -0
- data/lib/net/ssh/authentication/methods/none.rb +34 -0
- data/lib/net/ssh/authentication/methods/password.rb +80 -0
- data/lib/net/ssh/authentication/methods/publickey.rb +95 -0
- data/lib/net/ssh/authentication/pageant.rb +497 -0
- data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
- data/lib/net/ssh/authentication/session.rb +163 -0
- data/lib/net/ssh/buffer.rb +434 -0
- data/lib/net/ssh/buffered_io.rb +202 -0
- data/lib/net/ssh/config.rb +406 -0
- data/lib/net/ssh/connection/channel.rb +695 -0
- data/lib/net/ssh/connection/constants.rb +33 -0
- data/lib/net/ssh/connection/event_loop.rb +123 -0
- data/lib/net/ssh/connection/keepalive.rb +59 -0
- data/lib/net/ssh/connection/session.rb +712 -0
- data/lib/net/ssh/connection/term.rb +180 -0
- data/lib/net/ssh/errors.rb +106 -0
- data/lib/net/ssh/key_factory.rb +218 -0
- data/lib/net/ssh/known_hosts.rb +264 -0
- data/lib/net/ssh/loggable.rb +62 -0
- data/lib/net/ssh/packet.rb +106 -0
- data/lib/net/ssh/prompt.rb +62 -0
- data/lib/net/ssh/proxy/command.rb +123 -0
- data/lib/net/ssh/proxy/errors.rb +16 -0
- data/lib/net/ssh/proxy/http.rb +98 -0
- data/lib/net/ssh/proxy/https.rb +50 -0
- data/lib/net/ssh/proxy/jump.rb +54 -0
- data/lib/net/ssh/proxy/socks4.rb +67 -0
- data/lib/net/ssh/proxy/socks5.rb +140 -0
- data/lib/net/ssh/service/forward.rb +426 -0
- data/lib/net/ssh/test/channel.rb +147 -0
- data/lib/net/ssh/test/extensions.rb +173 -0
- data/lib/net/ssh/test/kex.rb +46 -0
- data/lib/net/ssh/test/local_packet.rb +53 -0
- data/lib/net/ssh/test/packet.rb +101 -0
- data/lib/net/ssh/test/remote_packet.rb +40 -0
- data/lib/net/ssh/test/script.rb +180 -0
- data/lib/net/ssh/test/socket.rb +65 -0
- data/lib/net/ssh/test.rb +94 -0
- data/lib/net/ssh/transport/algorithms.rb +502 -0
- data/lib/net/ssh/transport/cipher_factory.rb +103 -0
- data/lib/net/ssh/transport/constants.rb +40 -0
- data/lib/net/ssh/transport/ctr.rb +115 -0
- data/lib/net/ssh/transport/hmac/abstract.rb +97 -0
- data/lib/net/ssh/transport/hmac/md5.rb +10 -0
- data/lib/net/ssh/transport/hmac/md5_96.rb +9 -0
- data/lib/net/ssh/transport/hmac/none.rb +13 -0
- data/lib/net/ssh/transport/hmac/ripemd160.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha1.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha1_96.rb +9 -0
- data/lib/net/ssh/transport/hmac/sha2_256.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha2_256_96.rb +9 -0
- data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac/sha2_512.rb +11 -0
- data/lib/net/ssh/transport/hmac/sha2_512_96.rb +9 -0
- data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac.rb +47 -0
- data/lib/net/ssh/transport/identity_cipher.rb +57 -0
- data/lib/net/ssh/transport/kex/abstract.rb +130 -0
- data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +37 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +122 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +72 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +11 -0
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +39 -0
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +21 -0
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +21 -0
- data/lib/net/ssh/transport/kex.rb +31 -0
- data/lib/net/ssh/transport/key_expander.rb +30 -0
- data/lib/net/ssh/transport/openssl.rb +253 -0
- data/lib/net/ssh/transport/packet_stream.rb +280 -0
- data/lib/net/ssh/transport/server_version.rb +77 -0
- data/lib/net/ssh/transport/session.rb +354 -0
- data/lib/net/ssh/transport/state.rb +208 -0
- data/lib/net/ssh/verifiers/accept_new.rb +33 -0
- data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
- data/lib/net/ssh/verifiers/always.rb +58 -0
- data/lib/net/ssh/verifiers/never.rb +19 -0
- data/lib/net/ssh/version.rb +68 -0
- data/lib/net/ssh.rb +330 -0
- data/net-ssh-public_cert.pem +20 -0
- data/net-ssh.gemspec +44 -0
- data/support/ssh_tunnel_bug.rb +65 -0
- metadata +271 -0
@@ -0,0 +1,695 @@
|
|
1
|
+
require 'net/ssh/loggable'
|
2
|
+
require 'net/ssh/connection/constants'
|
3
|
+
require 'net/ssh/connection/term'
|
4
|
+
|
5
|
+
module Net
|
6
|
+
module SSH
|
7
|
+
module Connection
|
8
|
+
# The channel abstraction. Multiple "channels" can be multiplexed onto a
|
9
|
+
# single SSH channel, each operating independently and seemingly in parallel.
|
10
|
+
# This class represents a single such channel. Most operations performed
|
11
|
+
# with the Net::SSH library will involve using one or more channels.
|
12
|
+
#
|
13
|
+
# Channels are intended to be used asynchronously. You request that one be
|
14
|
+
# opened (via Connection::Session#open_channel), and when it is opened, your
|
15
|
+
# callback is invoked. Then, you set various other callbacks on the newly
|
16
|
+
# opened channel, which are called in response to the corresponding events.
|
17
|
+
# Programming with Net::SSH works best if you think of your programs as
|
18
|
+
# state machines. Complex programs are best implemented as objects that
|
19
|
+
# wrap a channel. See Net::SCP and Net::SFTP for examples of how complex
|
20
|
+
# state machines can be built on top of the SSH protocol.
|
21
|
+
#
|
22
|
+
# ssh.open_channel do |channel|
|
23
|
+
# channel.exec("/invoke/some/command") do |ch, success|
|
24
|
+
# abort "could not execute command" unless success
|
25
|
+
#
|
26
|
+
# channel.on_data do |ch, data|
|
27
|
+
# puts "got stdout: #{data}"
|
28
|
+
# channel.send_data "something for stdin\n"
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# channel.on_extended_data do |ch, type, data|
|
32
|
+
# puts "got stderr: #{data}"
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# channel.on_close do |ch|
|
36
|
+
# puts "channel is closing!"
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# ssh.loop
|
42
|
+
#
|
43
|
+
# Channels also have a basic hash-like interface, that allows programs to
|
44
|
+
# store arbitrary state information on a channel object. This helps simplify
|
45
|
+
# the writing of state machines, especially when you may be juggling
|
46
|
+
# multiple open channels at the same time.
|
47
|
+
#
|
48
|
+
# Note that data sent across SSH channels are governed by maximum packet
|
49
|
+
# sizes and maximum window sizes. These details are managed internally
|
50
|
+
# by Net::SSH::Connection::Channel, so you may remain blissfully ignorant
|
51
|
+
# if you so desire, but you can always inspect the current maximums, as
|
52
|
+
# well as the remaining window size, using the reader attributes for those
|
53
|
+
# values.
|
54
|
+
class Channel
|
55
|
+
include Loggable
|
56
|
+
include Constants
|
57
|
+
|
58
|
+
# The local id for this channel, assigned by the Net::SSH::Connection::Session instance.
|
59
|
+
attr_reader :local_id
|
60
|
+
|
61
|
+
# The remote id for this channel, assigned by the remote host.
|
62
|
+
attr_reader :remote_id
|
63
|
+
|
64
|
+
# The type of this channel, usually "session".
|
65
|
+
attr_reader :type
|
66
|
+
|
67
|
+
# The underlying Net::SSH::Connection::Session instance that supports this channel.
|
68
|
+
attr_reader :connection
|
69
|
+
|
70
|
+
# The maximum packet size that the local host can receive.
|
71
|
+
attr_reader :local_maximum_packet_size
|
72
|
+
|
73
|
+
# The maximum amount of data that the local end of this channel can
|
74
|
+
# receive. This is a total, not per-packet.
|
75
|
+
attr_reader :local_maximum_window_size
|
76
|
+
|
77
|
+
# The maximum packet size that the remote host can receive.
|
78
|
+
attr_reader :remote_maximum_packet_size
|
79
|
+
|
80
|
+
# The maximum amount of data that the remote end of this channel can
|
81
|
+
# receive. This is a total, not per-packet.
|
82
|
+
attr_reader :remote_maximum_window_size
|
83
|
+
|
84
|
+
# This is the remaining window size on the local end of this channel. When
|
85
|
+
# this reaches zero, no more data can be received.
|
86
|
+
attr_reader :local_window_size
|
87
|
+
|
88
|
+
# This is the remaining window size on the remote end of this channel. When
|
89
|
+
# this reaches zero, no more data can be sent.
|
90
|
+
attr_reader :remote_window_size
|
91
|
+
|
92
|
+
# A hash of properties for this channel. These can be used to store state
|
93
|
+
# information about this channel. See also #[] and #[]=.
|
94
|
+
attr_reader :properties
|
95
|
+
|
96
|
+
# The output buffer for this channel. Data written to the channel is
|
97
|
+
# enqueued here, to be written as CHANNEL_DATA packets during each pass of
|
98
|
+
# the event loop. See Connection::Session#process and #enqueue_pending_output.
|
99
|
+
attr_reader :output #:nodoc:
|
100
|
+
|
101
|
+
# The list of pending requests. Each time a request is sent which requires
|
102
|
+
# a reply, the corresponding callback is pushed onto this queue. As responses
|
103
|
+
# arrive, they are shifted off the front and handled.
|
104
|
+
attr_reader :pending_requests #:nodoc:
|
105
|
+
|
106
|
+
# Instantiates a new channel on the given connection, of the given type,
|
107
|
+
# and with the given id. If a block is given, it will be remembered until
|
108
|
+
# the channel is confirmed open by the server, and will be invoked at
|
109
|
+
# that time (see #do_open_confirmation).
|
110
|
+
#
|
111
|
+
# This also sets the default maximum packet size and maximum window size.
|
112
|
+
def initialize(connection, type, local_id, max_pkt_size = 0x8000, max_win_size = 0x20000, &on_confirm_open)
|
113
|
+
self.logger = connection.logger
|
114
|
+
|
115
|
+
@connection = connection
|
116
|
+
@type = type
|
117
|
+
@local_id = local_id
|
118
|
+
|
119
|
+
@local_maximum_packet_size = max_pkt_size
|
120
|
+
@local_window_size = @local_maximum_window_size = max_win_size
|
121
|
+
|
122
|
+
@on_confirm_open = on_confirm_open
|
123
|
+
|
124
|
+
@output = Buffer.new
|
125
|
+
|
126
|
+
@properties = {}
|
127
|
+
|
128
|
+
@pending_requests = []
|
129
|
+
@on_open_failed = @on_data = @on_extended_data = @on_process = @on_close = @on_eof = nil
|
130
|
+
@on_request = {}
|
131
|
+
@closing = @eof = @sent_eof = @local_closed = @remote_closed = false
|
132
|
+
end
|
133
|
+
|
134
|
+
# A shortcut for accessing properties of the channel (see #properties).
|
135
|
+
def [](name)
|
136
|
+
@properties[name]
|
137
|
+
end
|
138
|
+
|
139
|
+
# A shortcut for setting properties of the channel (see #properties).
|
140
|
+
def []=(name, value)
|
141
|
+
@properties[name] = value
|
142
|
+
end
|
143
|
+
|
144
|
+
# Syntactic sugar for executing a command. Sends a channel request asking
|
145
|
+
# that the given command be invoked. If the block is given, it will be
|
146
|
+
# called when the server responds. The first parameter will be the
|
147
|
+
# channel, and the second will be true or false, indicating whether the
|
148
|
+
# request succeeded or not. In this case, success means that the command
|
149
|
+
# is being executed, not that it has completed, and failure means that the
|
150
|
+
# command altogether failed to be executed.
|
151
|
+
#
|
152
|
+
# channel.exec "ls -l /home" do |ch, success|
|
153
|
+
# if success
|
154
|
+
# puts "command has begun executing..."
|
155
|
+
# # this is a good place to hang callbacks like #on_data...
|
156
|
+
# else
|
157
|
+
# puts "alas! the command could not be invoked!"
|
158
|
+
# end
|
159
|
+
# end
|
160
|
+
def exec(command, &block)
|
161
|
+
send_channel_request("exec", :string, command, &block)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Syntactic sugar for requesting that a subsystem be started. Subsystems
|
165
|
+
# are a way for other protocols (like SFTP) to be run, using SSH as
|
166
|
+
# the transport. Generally, you'll never need to call this directly unless
|
167
|
+
# you are the implementor of something that consumes an SSH subsystem, like
|
168
|
+
# SFTP.
|
169
|
+
#
|
170
|
+
# channel.subsystem("sftp") do |ch, success|
|
171
|
+
# if success
|
172
|
+
# puts "subsystem successfully started"
|
173
|
+
# else
|
174
|
+
# puts "subsystem could not be started"
|
175
|
+
# end
|
176
|
+
# end
|
177
|
+
def subsystem(subsystem, &block)
|
178
|
+
send_channel_request("subsystem", :string, subsystem, &block)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Syntactic sugar for setting an environment variable in the remote
|
182
|
+
# process' environment. Note that for security reasons, the server may
|
183
|
+
# refuse to set certain environment variables, or all, at the server's
|
184
|
+
# discretion. If you are connecting to an OpenSSH server, you will
|
185
|
+
# need to update the AcceptEnv setting in the sshd_config to include the
|
186
|
+
# environment variables you want to send.
|
187
|
+
#
|
188
|
+
# channel.env "PATH", "/usr/local/bin"
|
189
|
+
def env(variable_name, variable_value, &block)
|
190
|
+
send_channel_request("env", :string, variable_name, :string, variable_value, &block)
|
191
|
+
end
|
192
|
+
|
193
|
+
# A hash of the valid PTY options (see #request_pty).
|
194
|
+
VALID_PTY_OPTIONS = { term: "xterm",
|
195
|
+
chars_wide: 80,
|
196
|
+
chars_high: 24,
|
197
|
+
pixels_wide: 640,
|
198
|
+
pixels_high: 480,
|
199
|
+
modes: {} }
|
200
|
+
|
201
|
+
# Requests that a pseudo-tty (or "pty") be made available for this channel.
|
202
|
+
# This is useful when you want to invoke and interact with some kind of
|
203
|
+
# screen-based program (e.g., vim, or some menuing system).
|
204
|
+
#
|
205
|
+
# Note, that without a pty some programs (e.g. sudo, or subversion) on
|
206
|
+
# some systems, will not be able to run interactively, and will error
|
207
|
+
# instead of prompt if they ever need some user interaction.
|
208
|
+
#
|
209
|
+
# Note, too, that when a pty is requested, user's shell configuration
|
210
|
+
# scripts (.bashrc and such) are not run by default, whereas they are
|
211
|
+
# run when a pty is not present.
|
212
|
+
#
|
213
|
+
# channel.request_pty do |ch, success|
|
214
|
+
# if success
|
215
|
+
# puts "pty successfully obtained"
|
216
|
+
# else
|
217
|
+
# puts "could not obtain pty"
|
218
|
+
# end
|
219
|
+
# end
|
220
|
+
def request_pty(opts={}, &block)
|
221
|
+
extra = opts.keys - VALID_PTY_OPTIONS.keys
|
222
|
+
raise ArgumentError, "invalid option(s) to request_pty: #{extra.inspect}" if extra.any?
|
223
|
+
|
224
|
+
opts = VALID_PTY_OPTIONS.merge(opts)
|
225
|
+
|
226
|
+
modes = opts[:modes].inject(Buffer.new) do |memo, (mode, data)|
|
227
|
+
memo.write_byte(mode).write_long(data)
|
228
|
+
end
|
229
|
+
# mark the end of the mode opcode list with a 0 byte
|
230
|
+
modes.write_byte(0)
|
231
|
+
|
232
|
+
send_channel_request("pty-req", :string, opts[:term],
|
233
|
+
:long, opts[:chars_wide], :long, opts[:chars_high],
|
234
|
+
:long, opts[:pixels_wide], :long, opts[:pixels_high],
|
235
|
+
:string, modes.to_s, &block)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Sends data to the channel's remote endpoint. This usually has the
|
239
|
+
# effect of sending the given string to the remote process' stdin stream.
|
240
|
+
# Note that it does not immediately send the data across the channel,
|
241
|
+
# but instead merely appends the given data to the channel's output buffer,
|
242
|
+
# preparatory to being packaged up and sent out the next time the connection
|
243
|
+
# is accepting data. (A connection might not be accepting data if, for
|
244
|
+
# instance, it has filled its data window and has not yet been resized by
|
245
|
+
# the remote end-point.)
|
246
|
+
#
|
247
|
+
# This will raise an exception if the channel has previously declared
|
248
|
+
# that no more data will be sent (see #eof!).
|
249
|
+
#
|
250
|
+
# channel.send_data("the password\n")
|
251
|
+
def send_data(data)
|
252
|
+
raise EOFError, "cannot send data if channel has declared eof" if eof?
|
253
|
+
|
254
|
+
output.append(data.to_s)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Returns true if the channel exists in the channel list of the session,
|
258
|
+
# and false otherwise. This can be used to determine whether a channel has
|
259
|
+
# been closed or not.
|
260
|
+
#
|
261
|
+
# ssh.loop { channel.active? }
|
262
|
+
def active?
|
263
|
+
connection.channels.key?(local_id)
|
264
|
+
end
|
265
|
+
|
266
|
+
# Runs the SSH event loop until the channel is no longer active. This is
|
267
|
+
# handy for blocking while you wait for some channel to finish.
|
268
|
+
#
|
269
|
+
# channel.exec("grep ...") { ... }
|
270
|
+
# channel.wait
|
271
|
+
def wait
|
272
|
+
connection.loop { active? }
|
273
|
+
end
|
274
|
+
|
275
|
+
# True if close() has been called; NOTE: if the channel has data waiting to
|
276
|
+
# be sent then the channel will close after all the data is sent. See
|
277
|
+
# closed?() to determine if we have actually sent CHANNEL_CLOSE to server.
|
278
|
+
# This may be true for awhile before closed? returns true if we are still
|
279
|
+
# sending buffered output to server.
|
280
|
+
def closing?
|
281
|
+
@closing
|
282
|
+
end
|
283
|
+
|
284
|
+
# True if we have sent CHANNEL_CLOSE to the remote server.
|
285
|
+
def local_closed?
|
286
|
+
@local_closed
|
287
|
+
end
|
288
|
+
|
289
|
+
def remote_closed?
|
290
|
+
@remote_closed
|
291
|
+
end
|
292
|
+
|
293
|
+
def remote_closed!
|
294
|
+
@remote_closed = true
|
295
|
+
end
|
296
|
+
|
297
|
+
# Requests that the channel be closed. It only marks the channel to be closed
|
298
|
+
# the CHANNEL_CLOSE message will be sent from event loop
|
299
|
+
def close
|
300
|
+
return if @closing
|
301
|
+
|
302
|
+
@closing = true
|
303
|
+
end
|
304
|
+
|
305
|
+
# Returns true if the local end of the channel has declared that no more
|
306
|
+
# data is forthcoming (see #eof!). Trying to send data via #send_data when
|
307
|
+
# this is true will result in an exception being raised.
|
308
|
+
def eof?
|
309
|
+
@eof
|
310
|
+
end
|
311
|
+
|
312
|
+
# Tells the remote end of the channel that no more data is forthcoming
|
313
|
+
# from this end of the channel. The remote end may still send data.
|
314
|
+
# The CHANNEL_EOF packet will be sent once the output buffer is empty.
|
315
|
+
def eof!
|
316
|
+
return if eof?
|
317
|
+
|
318
|
+
@eof = true
|
319
|
+
end
|
320
|
+
|
321
|
+
# If an #on_process handler has been set up, this will cause it to be
|
322
|
+
# invoked (passing the channel itself as an argument). It also causes all
|
323
|
+
# pending output to be enqueued as CHANNEL_DATA packets (see #enqueue_pending_output).
|
324
|
+
def process
|
325
|
+
@on_process.call(self) if @on_process
|
326
|
+
enqueue_pending_output
|
327
|
+
|
328
|
+
if @eof and not @sent_eof and output.empty? and remote_id and not @local_closed
|
329
|
+
connection.send_message(Buffer.from(:byte, CHANNEL_EOF, :long, remote_id))
|
330
|
+
@sent_eof = true
|
331
|
+
end
|
332
|
+
|
333
|
+
if @closing and not @local_closed and output.empty? and remote_id
|
334
|
+
connection.send_message(Buffer.from(:byte, CHANNEL_CLOSE, :long, remote_id))
|
335
|
+
@local_closed = true
|
336
|
+
connection.cleanup_channel(self)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
# Registers a callback to be invoked when data packets are received by the
|
341
|
+
# channel. The callback is called with the channel as the first argument,
|
342
|
+
# and the data as the second.
|
343
|
+
#
|
344
|
+
# channel.on_data do |ch, data|
|
345
|
+
# puts "got data: #{data.inspect}"
|
346
|
+
# end
|
347
|
+
#
|
348
|
+
# Data received this way is typically the data written by the remote
|
349
|
+
# process to its +stdout+ stream.
|
350
|
+
def on_data(&block)
|
351
|
+
old, @on_data = @on_data, block
|
352
|
+
old
|
353
|
+
end
|
354
|
+
|
355
|
+
# Registers a callback to be invoked when extended data packets are received
|
356
|
+
# by the channel. The callback is called with the channel as the first
|
357
|
+
# argument, the data type (as an integer) as the second, and the data as
|
358
|
+
# the third. Extended data is almost exclusively used to send +stderr+ data
|
359
|
+
# (+type+ == 1). Other extended data types are not defined by the SSH
|
360
|
+
# protocol.
|
361
|
+
#
|
362
|
+
# channel.on_extended_data do |ch, type, data|
|
363
|
+
# puts "got stderr: #{data.inspect}"
|
364
|
+
# end
|
365
|
+
def on_extended_data(&block)
|
366
|
+
old, @on_extended_data = @on_extended_data, block
|
367
|
+
old
|
368
|
+
end
|
369
|
+
|
370
|
+
# Registers a callback to be invoked for each pass of the event loop for
|
371
|
+
# this channel. There are no guarantees on timeliness in the event loop,
|
372
|
+
# but it will be called roughly once for each packet received by the
|
373
|
+
# connection (not the channel). This callback is invoked with the channel
|
374
|
+
# as the sole argument.
|
375
|
+
#
|
376
|
+
# Here's an example that accumulates the channel data into a variable on
|
377
|
+
# the channel itself, and displays individual lines in the input one
|
378
|
+
# at a time when the channel is processed:
|
379
|
+
#
|
380
|
+
# channel[:data] = ""
|
381
|
+
#
|
382
|
+
# channel.on_data do |ch, data|
|
383
|
+
# channel[:data] << data
|
384
|
+
# end
|
385
|
+
#
|
386
|
+
# channel.on_process do |ch|
|
387
|
+
# if channel[:data] =~ /^.*?\n/
|
388
|
+
# puts $&
|
389
|
+
# channel[:data] = $'
|
390
|
+
# end
|
391
|
+
# end
|
392
|
+
def on_process(&block)
|
393
|
+
old, @on_process = @on_process, block
|
394
|
+
old
|
395
|
+
end
|
396
|
+
|
397
|
+
# Registers a callback to be invoked when the server acknowledges that a
|
398
|
+
# channel is closed. This is invoked with the channel as the sole argument.
|
399
|
+
#
|
400
|
+
# channel.on_close do |ch|
|
401
|
+
# puts "remote end is closing!"
|
402
|
+
# end
|
403
|
+
def on_close(&block)
|
404
|
+
old, @on_close = @on_close, block
|
405
|
+
old
|
406
|
+
end
|
407
|
+
|
408
|
+
# Registers a callback to be invoked when the server indicates that no more
|
409
|
+
# data will be sent to the channel (although the channel can still send
|
410
|
+
# data to the server). The channel is the sole argument to the callback.
|
411
|
+
#
|
412
|
+
# channel.on_eof do |ch|
|
413
|
+
# puts "remote end is done sending data"
|
414
|
+
# end
|
415
|
+
def on_eof(&block)
|
416
|
+
old, @on_eof = @on_eof, block
|
417
|
+
old
|
418
|
+
end
|
419
|
+
|
420
|
+
# Registers a callback to be invoked when the server was unable to open
|
421
|
+
# the requested channel. The channel itself will be passed to the block,
|
422
|
+
# along with the integer "reason code" for the failure, and a textual
|
423
|
+
# description of the failure from the server.
|
424
|
+
#
|
425
|
+
# channel = session.open_channel do |ch|
|
426
|
+
# # ..
|
427
|
+
# end
|
428
|
+
#
|
429
|
+
# channel.on_open_failed { |ch, code, desc| ... }
|
430
|
+
def on_open_failed(&block)
|
431
|
+
old, @on_open_failed = @on_open_failed, block
|
432
|
+
old
|
433
|
+
end
|
434
|
+
|
435
|
+
# Registers a callback to be invoked when a channel request of the given
|
436
|
+
# type is received. The callback will receive the channel as the first
|
437
|
+
# argument, and the associated (unparsed) data as the second. The data
|
438
|
+
# will be a Net::SSH::Buffer that you will need to parse, yourself,
|
439
|
+
# according to the kind of request you are watching.
|
440
|
+
#
|
441
|
+
# By default, if the request wants a reply, Net::SSH will send a
|
442
|
+
# CHANNEL_SUCCESS response for any request that was handled by a registered
|
443
|
+
# callback, and CHANNEL_FAILURE for any that wasn't, but if you want your
|
444
|
+
# registered callback to result in a CHANNEL_FAILURE response, just raise
|
445
|
+
# Net::SSH::ChannelRequestFailed.
|
446
|
+
#
|
447
|
+
# Some common channel requests that your programs might want to listen
|
448
|
+
# for are:
|
449
|
+
#
|
450
|
+
# * "exit-status" : the exit status of the remote process will be reported
|
451
|
+
# as a long integer in the data buffer, which you can grab via
|
452
|
+
# data.read_long.
|
453
|
+
# * "exit-signal" : if the remote process died as a result of a signal
|
454
|
+
# being sent to it, the signal will be reported as a string in the
|
455
|
+
# data, via data.read_string. (Not all SSH servers support this channel
|
456
|
+
# request type.)
|
457
|
+
#
|
458
|
+
# channel.on_request "exit-status" do |ch, data|
|
459
|
+
# puts "process terminated with exit status: #{data.read_long}"
|
460
|
+
# end
|
461
|
+
def on_request(type, &block)
|
462
|
+
old, @on_request[type] = @on_request[type], block
|
463
|
+
old
|
464
|
+
end
|
465
|
+
|
466
|
+
# Sends a new channel request with the given name. The extra +data+
|
467
|
+
# parameter must either be empty, or consist of an even number of
|
468
|
+
# arguments. See Net::SSH::Buffer.from for a description of their format.
|
469
|
+
# If a block is given, it is registered as a callback for a pending
|
470
|
+
# request, and the packet will be flagged so that the server knows a
|
471
|
+
# reply is required. If no block is given, the server will send no
|
472
|
+
# response to this request. Responses, where required, will cause the
|
473
|
+
# callback to be invoked with the channel as the first argument, and
|
474
|
+
# either true or false as the second, depending on whether the request
|
475
|
+
# succeeded or not. The meaning of "success" and "failure" in this context
|
476
|
+
# is dependent on the specific request that was sent.
|
477
|
+
#
|
478
|
+
# channel.send_channel_request "shell" do |ch, success|
|
479
|
+
# if success
|
480
|
+
# puts "user shell started successfully"
|
481
|
+
# else
|
482
|
+
# puts "could not start user shell"
|
483
|
+
# end
|
484
|
+
# end
|
485
|
+
#
|
486
|
+
# Most channel requests you'll want to send are already wrapped in more
|
487
|
+
# convenient helper methods (see #exec and #subsystem).
|
488
|
+
def send_channel_request(request_name, *data, &callback)
|
489
|
+
info { "sending channel request #{request_name.inspect}" }
|
490
|
+
fail "Channel open not yet confirmed, please call send_channel_request(or exec) from block of open_channel" unless remote_id
|
491
|
+
|
492
|
+
msg = Buffer.from(:byte, CHANNEL_REQUEST,
|
493
|
+
:long, remote_id, :string, request_name,
|
494
|
+
:bool, !callback.nil?, *data)
|
495
|
+
connection.send_message(msg)
|
496
|
+
pending_requests << callback if callback
|
497
|
+
end
|
498
|
+
|
499
|
+
public # these methods are public, but for Net::SSH internal use only
|
500
|
+
|
501
|
+
# Enqueues pending output at the connection as CHANNEL_DATA packets. This
|
502
|
+
# does nothing if the channel has not yet been confirmed open (see
|
503
|
+
# #do_open_confirmation). This is called automatically by #process, which
|
504
|
+
# is called from the event loop (Connection::Session#process). You will
|
505
|
+
# generally not need to invoke it directly.
|
506
|
+
def enqueue_pending_output #:nodoc:
|
507
|
+
return unless remote_id
|
508
|
+
|
509
|
+
while output.length > 0
|
510
|
+
length = output.length
|
511
|
+
length = remote_window_size if length > remote_window_size
|
512
|
+
length = remote_maximum_packet_size if length > remote_maximum_packet_size
|
513
|
+
|
514
|
+
if length > 0
|
515
|
+
connection.send_message(Buffer.from(:byte, CHANNEL_DATA, :long, remote_id, :string, output.read(length)))
|
516
|
+
output.consume!
|
517
|
+
@remote_window_size -= length
|
518
|
+
else
|
519
|
+
break
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
# Invoked when the server confirms that a channel has been opened.
|
525
|
+
# The remote_id is the id of the channel as assigned by the remote host,
|
526
|
+
# and max_window and max_packet are the maximum window and maximum
|
527
|
+
# packet sizes, respectively. If an open-confirmation callback was
|
528
|
+
# given when the channel was created, it is invoked at this time with
|
529
|
+
# the channel itself as the sole argument.
|
530
|
+
def do_open_confirmation(remote_id, max_window, max_packet) #:nodoc:
|
531
|
+
@remote_id = remote_id
|
532
|
+
@remote_window_size = @remote_maximum_window_size = max_window
|
533
|
+
@remote_maximum_packet_size = max_packet
|
534
|
+
connection.forward.agent(self) if connection.options[:forward_agent] && type == "session"
|
535
|
+
forward_local_env(connection.options[:send_env]) if connection.options[:send_env]
|
536
|
+
set_remote_env(connection.options[:set_env]) if connection.options[:set_env]
|
537
|
+
@on_confirm_open.call(self) if @on_confirm_open
|
538
|
+
end
|
539
|
+
|
540
|
+
# Invoked when the server failed to open the channel. If an #on_open_failed
|
541
|
+
# callback was specified, it will be invoked with the channel, reason code,
|
542
|
+
# and description as arguments. Otherwise, a ChannelOpenFailed exception
|
543
|
+
# will be raised.
|
544
|
+
def do_open_failed(reason_code, description)
|
545
|
+
if @on_open_failed
|
546
|
+
@on_open_failed.call(self, reason_code, description)
|
547
|
+
else
|
548
|
+
raise ChannelOpenFailed.new(reason_code, description)
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
# Invoked when the server sends a CHANNEL_WINDOW_ADJUST packet, and
|
553
|
+
# causes the remote window size to be adjusted upwards by the given
|
554
|
+
# number of bytes. This has the effect of allowing more data to be sent
|
555
|
+
# from the local end to the remote end of the channel.
|
556
|
+
def do_window_adjust(bytes) #:nodoc:
|
557
|
+
@remote_maximum_window_size += bytes
|
558
|
+
@remote_window_size += bytes
|
559
|
+
end
|
560
|
+
|
561
|
+
# Invoked when the server sends a channel request. If any #on_request
|
562
|
+
# callback has been registered for the specific type of this request,
|
563
|
+
# it is invoked. If +want_reply+ is true, a packet will be sent of
|
564
|
+
# either CHANNEL_SUCCESS or CHANNEL_FAILURE type. If there was no callback
|
565
|
+
# to handle the request, CHANNEL_FAILURE will be sent. Otherwise,
|
566
|
+
# CHANNEL_SUCCESS, unless the callback raised ChannelRequestFailed. The
|
567
|
+
# callback should accept the channel as the first argument, and the
|
568
|
+
# request-specific data as the second.
|
569
|
+
def do_request(request, want_reply, data) #:nodoc:
|
570
|
+
result = true
|
571
|
+
|
572
|
+
begin
|
573
|
+
callback = @on_request[request] or raise ChannelRequestFailed
|
574
|
+
callback.call(self, data)
|
575
|
+
rescue ChannelRequestFailed
|
576
|
+
result = false
|
577
|
+
end
|
578
|
+
|
579
|
+
if want_reply
|
580
|
+
msg = Buffer.from(:byte, result ? CHANNEL_SUCCESS : CHANNEL_FAILURE, :long, remote_id)
|
581
|
+
connection.send_message(msg)
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
# Invokes the #on_data callback when the server sends data to the
|
586
|
+
# channel. This will reduce the available window size on the local end,
|
587
|
+
# but does not actually throttle requests that come in illegally when
|
588
|
+
# the window size is too small. The callback is invoked with the channel
|
589
|
+
# as the first argument, and the data as the second.
|
590
|
+
def do_data(data) #:nodoc:
|
591
|
+
update_local_window_size(data.length)
|
592
|
+
@on_data.call(self, data) if @on_data
|
593
|
+
end
|
594
|
+
|
595
|
+
# Invokes the #on_extended_data callback when the server sends
|
596
|
+
# extended data to the channel. This will reduce the available window
|
597
|
+
# size on the local end. The callback is invoked with the channel,
|
598
|
+
# type, and data.
|
599
|
+
def do_extended_data(type, data)
|
600
|
+
update_local_window_size(data.length)
|
601
|
+
@on_extended_data.call(self, type, data) if @on_extended_data
|
602
|
+
end
|
603
|
+
|
604
|
+
# Invokes the #on_eof callback when the server indicates that no
|
605
|
+
# further data is forthcoming. The callback is invoked with the channel
|
606
|
+
# as the argument.
|
607
|
+
def do_eof
|
608
|
+
@on_eof.call(self) if @on_eof
|
609
|
+
end
|
610
|
+
|
611
|
+
# Invokes the #on_close callback when the server closes a channel.
|
612
|
+
# The channel is the only argument.
|
613
|
+
def do_close
|
614
|
+
@on_close.call(self) if @on_close
|
615
|
+
end
|
616
|
+
|
617
|
+
# Invokes the next pending request callback with +false+ as the second
|
618
|
+
# argument.
|
619
|
+
def do_failure
|
620
|
+
if callback = pending_requests.shift
|
621
|
+
callback.call(self, false)
|
622
|
+
else
|
623
|
+
error { "channel failure received with no pending request to handle it (bug?)" }
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
# Invokes the next pending request callback with +true+ as the second
|
628
|
+
# argument.
|
629
|
+
def do_success
|
630
|
+
if callback = pending_requests.shift
|
631
|
+
callback.call(self, true)
|
632
|
+
else
|
633
|
+
error { "channel success received with no pending request to handle it (bug?)" }
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
private
|
638
|
+
|
639
|
+
# Runs the SSH event loop until the remote confirmed channel open
|
640
|
+
# experimental api
|
641
|
+
def wait_until_open_confirmed
|
642
|
+
connection.loop { !remote_id }
|
643
|
+
end
|
644
|
+
|
645
|
+
LOCAL_WINDOW_SIZE_INCREMENT = 0x20000
|
646
|
+
GOOD_LOCAL_MAXIUMUM_WINDOW_SIZE = 10 * LOCAL_WINDOW_SIZE_INCREMENT
|
647
|
+
|
648
|
+
# Updates the local window size by the given amount. If the window
|
649
|
+
# size drops to less than half of the local maximum (an arbitrary
|
650
|
+
# threshold), a CHANNEL_WINDOW_ADJUST message will be sent to the
|
651
|
+
# server telling it that the window size has grown.
|
652
|
+
def update_local_window_size(size)
|
653
|
+
@local_window_size -= size
|
654
|
+
if local_window_size < local_maximum_window_size / 2
|
655
|
+
connection.send_message(
|
656
|
+
Buffer.from(:byte, CHANNEL_WINDOW_ADJUST, :long, remote_id, :long, LOCAL_WINDOW_SIZE_INCREMENT)
|
657
|
+
)
|
658
|
+
@local_window_size += LOCAL_WINDOW_SIZE_INCREMENT
|
659
|
+
|
660
|
+
if @local_maximum_window_size < @local_window_size || @local_maximum_window_size < GOOD_LOCAL_MAXIUMUM_WINDOW_SIZE
|
661
|
+
@local_maximum_window_size += LOCAL_WINDOW_SIZE_INCREMENT
|
662
|
+
end
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
# Gets an +Array+ of local environment variables in the remote process'
|
667
|
+
# environment.
|
668
|
+
# A variable name can either be described by a +Regexp+ or +String+.
|
669
|
+
#
|
670
|
+
# channel.forward_local_env [/^GIT_.*$/, "LANG"]
|
671
|
+
def forward_local_env(env_variable_patterns)
|
672
|
+
Array(env_variable_patterns).each do |env_variable_pattern|
|
673
|
+
matched_variables = ENV.find_all do |env_name, _|
|
674
|
+
case env_variable_pattern
|
675
|
+
when Regexp then env_name =~ env_variable_pattern
|
676
|
+
when String then env_name == env_variable_pattern
|
677
|
+
end
|
678
|
+
end
|
679
|
+
matched_variables.each do |env_name, env_value|
|
680
|
+
self.env(env_name, env_value)
|
681
|
+
end
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
# Set a +Hash+ of environment variables in the remote process' environment.
|
686
|
+
#
|
687
|
+
# channel.set_remote_env foo: 'bar', baz: 'whale'
|
688
|
+
def set_remote_env(env)
|
689
|
+
env.each { |key, value| puts "E:#{key} V:#{value}" }
|
690
|
+
env.each { |key, value| self.env(key, value) }
|
691
|
+
end
|
692
|
+
end
|
693
|
+
end
|
694
|
+
end
|
695
|
+
end
|