eximius-net-ssh 6.3.1

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