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.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +93 -0
  3. data/.gitignore +13 -0
  4. data/.rubocop.yml +21 -0
  5. data/.rubocop_todo.yml +1074 -0
  6. data/.travis.yml +51 -0
  7. data/CHANGES.txt +698 -0
  8. data/Gemfile +13 -0
  9. data/Gemfile.noed25519 +12 -0
  10. data/ISSUE_TEMPLATE.md +30 -0
  11. data/LICENSE.txt +19 -0
  12. data/Manifest +132 -0
  13. data/README.md +287 -0
  14. data/Rakefile +105 -0
  15. data/THANKS.txt +110 -0
  16. data/appveyor.yml +58 -0
  17. data/lib/net/ssh/authentication/agent.rb +284 -0
  18. data/lib/net/ssh/authentication/certificate.rb +183 -0
  19. data/lib/net/ssh/authentication/constants.rb +20 -0
  20. data/lib/net/ssh/authentication/ed25519.rb +185 -0
  21. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  22. data/lib/net/ssh/authentication/key_manager.rb +297 -0
  23. data/lib/net/ssh/authentication/methods/abstract.rb +69 -0
  24. data/lib/net/ssh/authentication/methods/hostbased.rb +72 -0
  25. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +77 -0
  26. data/lib/net/ssh/authentication/methods/none.rb +34 -0
  27. data/lib/net/ssh/authentication/methods/password.rb +80 -0
  28. data/lib/net/ssh/authentication/methods/publickey.rb +95 -0
  29. data/lib/net/ssh/authentication/pageant.rb +497 -0
  30. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  31. data/lib/net/ssh/authentication/session.rb +163 -0
  32. data/lib/net/ssh/buffer.rb +434 -0
  33. data/lib/net/ssh/buffered_io.rb +202 -0
  34. data/lib/net/ssh/config.rb +406 -0
  35. data/lib/net/ssh/connection/channel.rb +695 -0
  36. data/lib/net/ssh/connection/constants.rb +33 -0
  37. data/lib/net/ssh/connection/event_loop.rb +123 -0
  38. data/lib/net/ssh/connection/keepalive.rb +59 -0
  39. data/lib/net/ssh/connection/session.rb +712 -0
  40. data/lib/net/ssh/connection/term.rb +180 -0
  41. data/lib/net/ssh/errors.rb +106 -0
  42. data/lib/net/ssh/key_factory.rb +218 -0
  43. data/lib/net/ssh/known_hosts.rb +264 -0
  44. data/lib/net/ssh/loggable.rb +62 -0
  45. data/lib/net/ssh/packet.rb +106 -0
  46. data/lib/net/ssh/prompt.rb +62 -0
  47. data/lib/net/ssh/proxy/command.rb +123 -0
  48. data/lib/net/ssh/proxy/errors.rb +16 -0
  49. data/lib/net/ssh/proxy/http.rb +98 -0
  50. data/lib/net/ssh/proxy/https.rb +50 -0
  51. data/lib/net/ssh/proxy/jump.rb +54 -0
  52. data/lib/net/ssh/proxy/socks4.rb +67 -0
  53. data/lib/net/ssh/proxy/socks5.rb +140 -0
  54. data/lib/net/ssh/service/forward.rb +426 -0
  55. data/lib/net/ssh/test/channel.rb +147 -0
  56. data/lib/net/ssh/test/extensions.rb +173 -0
  57. data/lib/net/ssh/test/kex.rb +46 -0
  58. data/lib/net/ssh/test/local_packet.rb +53 -0
  59. data/lib/net/ssh/test/packet.rb +101 -0
  60. data/lib/net/ssh/test/remote_packet.rb +40 -0
  61. data/lib/net/ssh/test/script.rb +180 -0
  62. data/lib/net/ssh/test/socket.rb +65 -0
  63. data/lib/net/ssh/test.rb +94 -0
  64. data/lib/net/ssh/transport/algorithms.rb +502 -0
  65. data/lib/net/ssh/transport/cipher_factory.rb +103 -0
  66. data/lib/net/ssh/transport/constants.rb +40 -0
  67. data/lib/net/ssh/transport/ctr.rb +115 -0
  68. data/lib/net/ssh/transport/hmac/abstract.rb +97 -0
  69. data/lib/net/ssh/transport/hmac/md5.rb +10 -0
  70. data/lib/net/ssh/transport/hmac/md5_96.rb +9 -0
  71. data/lib/net/ssh/transport/hmac/none.rb +13 -0
  72. data/lib/net/ssh/transport/hmac/ripemd160.rb +11 -0
  73. data/lib/net/ssh/transport/hmac/sha1.rb +11 -0
  74. data/lib/net/ssh/transport/hmac/sha1_96.rb +9 -0
  75. data/lib/net/ssh/transport/hmac/sha2_256.rb +11 -0
  76. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +9 -0
  77. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  78. data/lib/net/ssh/transport/hmac/sha2_512.rb +11 -0
  79. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +9 -0
  80. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  81. data/lib/net/ssh/transport/hmac.rb +47 -0
  82. data/lib/net/ssh/transport/identity_cipher.rb +57 -0
  83. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  84. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  85. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  86. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  87. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +37 -0
  88. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  89. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +122 -0
  90. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +72 -0
  91. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +11 -0
  92. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +39 -0
  93. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +21 -0
  94. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +21 -0
  95. data/lib/net/ssh/transport/kex.rb +31 -0
  96. data/lib/net/ssh/transport/key_expander.rb +30 -0
  97. data/lib/net/ssh/transport/openssl.rb +253 -0
  98. data/lib/net/ssh/transport/packet_stream.rb +280 -0
  99. data/lib/net/ssh/transport/server_version.rb +77 -0
  100. data/lib/net/ssh/transport/session.rb +354 -0
  101. data/lib/net/ssh/transport/state.rb +208 -0
  102. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  103. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  104. data/lib/net/ssh/verifiers/always.rb +58 -0
  105. data/lib/net/ssh/verifiers/never.rb +19 -0
  106. data/lib/net/ssh/version.rb +68 -0
  107. data/lib/net/ssh.rb +330 -0
  108. data/net-ssh-public_cert.pem +20 -0
  109. data/net-ssh.gemspec +44 -0
  110. data/support/ssh_tunnel_bug.rb +65 -0
  111. 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