net-ssh-net-ssh 2.0.12

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 (105) hide show
  1. data/CHANGELOG.rdoc +137 -0
  2. data/Manifest +104 -0
  3. data/README.rdoc +110 -0
  4. data/Rakefile +79 -0
  5. data/THANKS.rdoc +16 -0
  6. data/lib/net/ssh.rb +215 -0
  7. data/lib/net/ssh/authentication/agent.rb +176 -0
  8. data/lib/net/ssh/authentication/constants.rb +18 -0
  9. data/lib/net/ssh/authentication/key_manager.rb +193 -0
  10. data/lib/net/ssh/authentication/methods/abstract.rb +60 -0
  11. data/lib/net/ssh/authentication/methods/hostbased.rb +71 -0
  12. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +66 -0
  13. data/lib/net/ssh/authentication/methods/password.rb +39 -0
  14. data/lib/net/ssh/authentication/methods/publickey.rb +92 -0
  15. data/lib/net/ssh/authentication/pageant.rb +183 -0
  16. data/lib/net/ssh/authentication/session.rb +134 -0
  17. data/lib/net/ssh/buffer.rb +340 -0
  18. data/lib/net/ssh/buffered_io.rb +149 -0
  19. data/lib/net/ssh/config.rb +181 -0
  20. data/lib/net/ssh/connection/channel.rb +625 -0
  21. data/lib/net/ssh/connection/constants.rb +33 -0
  22. data/lib/net/ssh/connection/session.rb +596 -0
  23. data/lib/net/ssh/connection/term.rb +178 -0
  24. data/lib/net/ssh/errors.rb +85 -0
  25. data/lib/net/ssh/key_factory.rb +102 -0
  26. data/lib/net/ssh/known_hosts.rb +129 -0
  27. data/lib/net/ssh/loggable.rb +61 -0
  28. data/lib/net/ssh/packet.rb +102 -0
  29. data/lib/net/ssh/prompt.rb +93 -0
  30. data/lib/net/ssh/proxy/errors.rb +14 -0
  31. data/lib/net/ssh/proxy/http.rb +94 -0
  32. data/lib/net/ssh/proxy/socks4.rb +70 -0
  33. data/lib/net/ssh/proxy/socks5.rb +129 -0
  34. data/lib/net/ssh/ruby_compat.rb +7 -0
  35. data/lib/net/ssh/service/forward.rb +267 -0
  36. data/lib/net/ssh/test.rb +89 -0
  37. data/lib/net/ssh/test/channel.rb +129 -0
  38. data/lib/net/ssh/test/extensions.rb +152 -0
  39. data/lib/net/ssh/test/kex.rb +44 -0
  40. data/lib/net/ssh/test/local_packet.rb +51 -0
  41. data/lib/net/ssh/test/packet.rb +81 -0
  42. data/lib/net/ssh/test/remote_packet.rb +38 -0
  43. data/lib/net/ssh/test/script.rb +157 -0
  44. data/lib/net/ssh/test/socket.rb +59 -0
  45. data/lib/net/ssh/transport/algorithms.rb +384 -0
  46. data/lib/net/ssh/transport/cipher_factory.rb +84 -0
  47. data/lib/net/ssh/transport/constants.rb +30 -0
  48. data/lib/net/ssh/transport/hmac.rb +31 -0
  49. data/lib/net/ssh/transport/hmac/abstract.rb +78 -0
  50. data/lib/net/ssh/transport/hmac/md5.rb +12 -0
  51. data/lib/net/ssh/transport/hmac/md5_96.rb +11 -0
  52. data/lib/net/ssh/transport/hmac/none.rb +15 -0
  53. data/lib/net/ssh/transport/hmac/sha1.rb +13 -0
  54. data/lib/net/ssh/transport/hmac/sha1_96.rb +11 -0
  55. data/lib/net/ssh/transport/identity_cipher.rb +55 -0
  56. data/lib/net/ssh/transport/kex.rb +13 -0
  57. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +208 -0
  58. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +77 -0
  59. data/lib/net/ssh/transport/openssl.rb +128 -0
  60. data/lib/net/ssh/transport/packet_stream.rb +230 -0
  61. data/lib/net/ssh/transport/server_version.rb +69 -0
  62. data/lib/net/ssh/transport/session.rb +276 -0
  63. data/lib/net/ssh/transport/state.rb +206 -0
  64. data/lib/net/ssh/verifiers/lenient.rb +30 -0
  65. data/lib/net/ssh/verifiers/null.rb +12 -0
  66. data/lib/net/ssh/verifiers/strict.rb +53 -0
  67. data/lib/net/ssh/version.rb +62 -0
  68. data/net-ssh.gemspec +128 -0
  69. data/setup.rb +1585 -0
  70. data/test/authentication/methods/common.rb +28 -0
  71. data/test/authentication/methods/test_abstract.rb +51 -0
  72. data/test/authentication/methods/test_hostbased.rb +114 -0
  73. data/test/authentication/methods/test_keyboard_interactive.rb +98 -0
  74. data/test/authentication/methods/test_password.rb +50 -0
  75. data/test/authentication/methods/test_publickey.rb +127 -0
  76. data/test/authentication/test_agent.rb +205 -0
  77. data/test/authentication/test_key_manager.rb +105 -0
  78. data/test/authentication/test_session.rb +93 -0
  79. data/test/common.rb +106 -0
  80. data/test/configs/eqsign +3 -0
  81. data/test/configs/exact_match +8 -0
  82. data/test/configs/wild_cards +14 -0
  83. data/test/connection/test_channel.rb +452 -0
  84. data/test/connection/test_session.rb +488 -0
  85. data/test/test_all.rb +6 -0
  86. data/test/test_buffer.rb +336 -0
  87. data/test/test_buffered_io.rb +63 -0
  88. data/test/test_config.rb +84 -0
  89. data/test/test_key_factory.rb +67 -0
  90. data/test/transport/hmac/test_md5.rb +39 -0
  91. data/test/transport/hmac/test_md5_96.rb +25 -0
  92. data/test/transport/hmac/test_none.rb +34 -0
  93. data/test/transport/hmac/test_sha1.rb +34 -0
  94. data/test/transport/hmac/test_sha1_96.rb +25 -0
  95. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +146 -0
  96. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +92 -0
  97. data/test/transport/test_algorithms.rb +302 -0
  98. data/test/transport/test_cipher_factory.rb +171 -0
  99. data/test/transport/test_hmac.rb +34 -0
  100. data/test/transport/test_identity_cipher.rb +40 -0
  101. data/test/transport/test_packet_stream.rb +435 -0
  102. data/test/transport/test_server_version.rb +68 -0
  103. data/test/transport/test_session.rb +315 -0
  104. data/test/transport/test_state.rb +173 -0
  105. metadata +162 -0
@@ -0,0 +1,33 @@
1
+ module Net; module SSH; module Connection
2
+
3
+ # Definitions of constants that are specific to the connection layer of the
4
+ # SSH protocol.
5
+ module Constants
6
+
7
+ #--
8
+ # Connection protocol generic messages
9
+ #++
10
+
11
+ GLOBAL_REQUEST = 80
12
+ REQUEST_SUCCESS = 81
13
+ REQUEST_FAILURE = 82
14
+
15
+ #--
16
+ # Channel related messages
17
+ #++
18
+
19
+ CHANNEL_OPEN = 90
20
+ CHANNEL_OPEN_CONFIRMATION = 91
21
+ CHANNEL_OPEN_FAILURE = 92
22
+ CHANNEL_WINDOW_ADJUST = 93
23
+ CHANNEL_DATA = 94
24
+ CHANNEL_EXTENDED_DATA = 95
25
+ CHANNEL_EOF = 96
26
+ CHANNEL_CLOSE = 97
27
+ CHANNEL_REQUEST = 98
28
+ CHANNEL_SUCCESS = 99
29
+ CHANNEL_FAILURE = 100
30
+
31
+ end
32
+
33
+ end; end end
@@ -0,0 +1,596 @@
1
+ require 'net/ssh/loggable'
2
+ require 'net/ssh/connection/channel'
3
+ require 'net/ssh/connection/constants'
4
+ require 'net/ssh/service/forward'
5
+
6
+ module Net; module SSH; module Connection
7
+
8
+ # A session class representing the connection service running on top of
9
+ # the SSH transport layer. It manages the creation of channels (see
10
+ # #open_channel), and the dispatching of messages to the various channels.
11
+ # It also encapsulates the SSH event loop (via #loop and #process),
12
+ # and serves as a central point-of-reference for all SSH-related services (e.g.
13
+ # port forwarding, SFTP, SCP, etc.).
14
+ #
15
+ # You will rarely (if ever) need to instantiate this class directly; rather,
16
+ # you'll almost always use Net::SSH.start to initialize a new network
17
+ # connection, authenticate a user, and return a new connection session,
18
+ # all in one call.
19
+ #
20
+ # Net::SSH.start("localhost", "user") do |ssh|
21
+ # # 'ssh' is an instance of Net::SSH::Connection::Session
22
+ # ssh.exec! "/etc/init.d/some_process start"
23
+ # end
24
+ class Session
25
+ include Constants, Loggable
26
+
27
+ # The underlying transport layer abstraction (see Net::SSH::Transport::Session).
28
+ attr_reader :transport
29
+
30
+ # The map of options that were used to initialize this instance.
31
+ attr_reader :options
32
+
33
+ # The collection of custom properties for this instance. (See #[] and #[]=).
34
+ attr_reader :properties
35
+
36
+ # The map of channels, each key being the local-id for the channel.
37
+ attr_reader :channels #:nodoc:
38
+
39
+ # The map of listeners that the event loop knows about. See #listen_to.
40
+ attr_reader :listeners #:nodoc:
41
+
42
+ # The map of specialized handlers for opening specific channel types. See
43
+ # #on_open_channel.
44
+ attr_reader :channel_open_handlers #:nodoc:
45
+
46
+ # The list of callbacks for pending requests. See #send_global_request.
47
+ attr_reader :pending_requests #:nodoc:
48
+
49
+ class NilChannel
50
+ def initialize(session)
51
+ @session = session
52
+ end
53
+
54
+ def method_missing(sym, *args)
55
+ @session.lwarn { "ignoring request #{sym.inspect} for non-existent (closed?) channel; probably ssh server bug" }
56
+ end
57
+ end
58
+
59
+ # Create a new connection service instance atop the given transport
60
+ # layer. Initializes the listeners to be only the underlying socket object.
61
+ def initialize(transport, options={})
62
+ self.logger = transport.logger
63
+
64
+ @transport = transport
65
+ @options = options
66
+
67
+ @channel_id_counter = -1
68
+ @channels = Hash.new(NilChannel.new(self))
69
+ @listeners = { transport.socket => nil }
70
+ @pending_requests = []
71
+ @channel_open_handlers = {}
72
+ @on_global_request = {}
73
+ @properties = (options[:properties] || {}).dup
74
+ end
75
+
76
+ # Retrieves a custom property from this instance. This can be used to
77
+ # store additional state in applications that must manage multiple
78
+ # SSH connections.
79
+ def [](key)
80
+ @properties[key]
81
+ end
82
+
83
+ # Sets a custom property for this instance.
84
+ def []=(key, value)
85
+ @properties[key] = value
86
+ end
87
+
88
+ # Returns the name of the host that was given to the transport layer to
89
+ # connect to.
90
+ def host
91
+ transport.host
92
+ end
93
+
94
+ # Returns true if the underlying transport has been closed. Note that
95
+ # this can be a little misleading, since if the remote server has
96
+ # closed the connection, the local end will still think it is open
97
+ # until the next operation on the socket. Nevertheless, this method can
98
+ # be useful if you just want to know if _you_ have closed the connection.
99
+ def closed?
100
+ transport.closed?
101
+ end
102
+
103
+ # Closes the session gracefully, blocking until all channels have
104
+ # successfully closed, and then closes the underlying transport layer
105
+ # connection.
106
+ def close
107
+ info { "closing remaining channels (#{channels.length} open)" }
108
+ channels.each { |id, channel| channel.close }
109
+ loop { channels.any? }
110
+ transport.close
111
+ end
112
+
113
+ # Performs a "hard" shutdown of the connection. In general, this should
114
+ # never be done, but it might be necessary (in a rescue clause, for instance,
115
+ # when the connection needs to close but you don't know the status of the
116
+ # underlying protocol's state).
117
+ def shutdown!
118
+ transport.shutdown!
119
+ end
120
+
121
+ # preserve a reference to Kernel#loop
122
+ alias :loop_forever :loop
123
+
124
+ # Returns +true+ if there are any channels currently active on this
125
+ # session. By default, this will not include "invisible" channels
126
+ # (such as those created by forwarding ports and such), but if you pass
127
+ # a +true+ value for +include_invisible+, then those will be counted.
128
+ #
129
+ # This can be useful for determining whether the event loop should continue
130
+ # to be run.
131
+ #
132
+ # ssh.loop { ssh.busy? }
133
+ def busy?(include_invisible=false)
134
+ if include_invisible
135
+ channels.any?
136
+ else
137
+ channels.any? { |id, ch| !ch[:invisible] }
138
+ end
139
+ end
140
+
141
+ # The main event loop. Calls #process until #process returns false. If a
142
+ # block is given, it is passed to #process, otherwise a default proc is
143
+ # used that just returns true if there are any channels active (see #busy?).
144
+ # The # +wait+ parameter is also passed through to #process (where it is
145
+ # interpreted as the maximum number of seconds to wait for IO.select to return).
146
+ #
147
+ # # loop for as long as there are any channels active
148
+ # ssh.loop
149
+ #
150
+ # # loop for as long as there are any channels active, but make sure
151
+ # # the event loop runs at least once per 0.1 second
152
+ # ssh.loop(0.1)
153
+ #
154
+ # # loop until ctrl-C is pressed
155
+ # int_pressed = false
156
+ # trap("INT") { int_pressed = true }
157
+ # ssh.loop(0.1) { not int_pressed }
158
+ def loop(wait=nil, &block)
159
+ running = block || Proc.new { busy? }
160
+ loop_forever { break unless process(wait, &running) }
161
+ end
162
+
163
+ # The core of the event loop. It processes a single iteration of the event
164
+ # loop. If a block is given, it should return false when the processing
165
+ # should abort, which causes #process to return false. Otherwise,
166
+ # #process returns true. The session itself is yielded to the block as its
167
+ # only argument.
168
+ #
169
+ # If +wait+ is nil (the default), this method will block until any of the
170
+ # monitored IO objects are ready to be read from or written to. If you want
171
+ # it to not block, you can pass 0, or you can pass any other numeric value
172
+ # to indicate that it should block for no more than that many seconds.
173
+ # Passing 0 is a good way to poll the connection, but if you do it too
174
+ # frequently it can make your CPU quite busy!
175
+ #
176
+ # This will also cause all active channels to be processed once each (see
177
+ # Net::SSH::Connection::Channel#on_process).
178
+ #
179
+ # # process multiple Net::SSH connections in parallel
180
+ # connections = [
181
+ # Net::SSH.start("host1", ...),
182
+ # Net::SSH.start("host2", ...)
183
+ # ]
184
+ #
185
+ # connections.each do |ssh|
186
+ # ssh.exec "grep something /in/some/files"
187
+ # end
188
+ #
189
+ # condition = Proc.new { |s| s.busy? }
190
+ #
191
+ # loop do
192
+ # connections.delete_if { |ssh| !ssh.process(0.1, &condition) }
193
+ # break if connections.empty?
194
+ # end
195
+ def process(wait=nil, &block)
196
+ return false unless preprocess(&block)
197
+
198
+ r = listeners.keys
199
+ w = r.select { |w2| w2.respond_to?(:pending_write?) && w2.pending_write? }
200
+ readers, writers, = IO.select(r, w, nil, wait)
201
+
202
+ postprocess(readers, writers)
203
+ end
204
+
205
+ # This is called internally as part of #process. It dispatches any
206
+ # available incoming packets, and then runs Net::SSH::Connection::Channel#process
207
+ # for any active channels. If a block is given, it is invoked at the
208
+ # start of the method and again at the end, and if the block ever returns
209
+ # false, this method returns false. Otherwise, it returns true.
210
+ def preprocess
211
+ return false if block_given? && !yield(self)
212
+ dispatch_incoming_packets
213
+ channels.each { |id, channel| channel.process unless channel.closing? }
214
+ return false if block_given? && !yield(self)
215
+ return true
216
+ end
217
+
218
+ # This is called internally as part of #process. It loops over the given
219
+ # arrays of reader IO's and writer IO's, processing them as needed, and
220
+ # then calls Net::SSH::Transport::Session#rekey_as_needed to allow the
221
+ # transport layer to rekey. Then returns true.
222
+ def postprocess(readers, writers)
223
+ Array(readers).each do |reader|
224
+ if listeners[reader]
225
+ listeners[reader].call(reader)
226
+ else
227
+ if reader.fill.zero?
228
+ reader.close
229
+ stop_listening_to(reader)
230
+ end
231
+ end
232
+ end
233
+
234
+ Array(writers).each do |writer|
235
+ writer.send_pending
236
+ end
237
+
238
+ transport.rekey_as_needed
239
+
240
+ return true
241
+ end
242
+
243
+ # Send a global request of the given type. The +extra+ parameters must
244
+ # be even in number, and conform to the same format as described for
245
+ # Net::SSH::Buffer.from. If a callback is not specified, the request will
246
+ # not require a response from the server, otherwise the server is required
247
+ # to respond and indicate whether the request was successful or not. This
248
+ # success or failure is indicated by the callback being invoked, with the
249
+ # first parameter being true or false (success, or failure), and the second
250
+ # being the packet itself.
251
+ #
252
+ # Generally, Net::SSH will manage global requests that need to be sent
253
+ # (e.g. port forward requests and such are handled in the Net::SSH::Service::Forward
254
+ # class, for instance). However, there may be times when you need to
255
+ # send a global request that isn't explicitly handled by Net::SSH, and so
256
+ # this method is available to you.
257
+ #
258
+ # ssh.send_global_request("keep-alive@openssh.com")
259
+ def send_global_request(type, *extra, &callback)
260
+ info { "sending global request #{type}" }
261
+ msg = Buffer.from(:byte, GLOBAL_REQUEST, :string, type.to_s, :bool, !callback.nil?, *extra)
262
+ send_message(msg)
263
+ pending_requests << callback if callback
264
+ self
265
+ end
266
+
267
+ # Requests that a new channel be opened. By default, the channel will be
268
+ # of type "session", but if you know what you're doing you can select any
269
+ # of the channel types supported by the SSH protocol. The +extra+ parameters
270
+ # must be even in number and conform to the same format as described for
271
+ # Net::SSH::Buffer.from. If a callback is given, it will be invoked when
272
+ # the server confirms that the channel opened successfully. The sole parameter
273
+ # for the callback is the channel object itself.
274
+ #
275
+ # In general, you'll use #open_channel without any arguments; the only
276
+ # time you'd want to set the channel type or pass additional initialization
277
+ # data is if you were implementing an SSH extension.
278
+ #
279
+ # channel = ssh.open_channel do |ch|
280
+ # ch.exec "grep something /some/files" do |ch, success|
281
+ # ...
282
+ # end
283
+ # end
284
+ #
285
+ # channel.wait
286
+ def open_channel(type="session", *extra, &on_confirm)
287
+ local_id = get_next_channel_id
288
+ channel = Channel.new(self, type, local_id, &on_confirm)
289
+
290
+ msg = Buffer.from(:byte, CHANNEL_OPEN, :string, type, :long, local_id,
291
+ :long, channel.local_maximum_window_size,
292
+ :long, channel.local_maximum_packet_size, *extra)
293
+ send_message(msg)
294
+
295
+ channels[local_id] = channel
296
+ end
297
+
298
+ # A convenience method for executing a command and interacting with it. If
299
+ # no block is given, all output is printed via $stdout and $stderr. Otherwise,
300
+ # the block is called for each data and extended data packet, with three
301
+ # arguments: the channel object, a symbol indicating the data type
302
+ # (:stdout or :stderr), and the data (as a string).
303
+ #
304
+ # Note that this method returns immediately, and requires an event loop
305
+ # (see Session#loop) in order for the command to actually execute.
306
+ #
307
+ # This is effectively identical to calling #open_channel, and then
308
+ # Net::SSH::Connection::Channel#exec, and then setting up the channel
309
+ # callbacks. However, for most uses, this will be sufficient.
310
+ #
311
+ # ssh.exec "grep something /some/files" do |ch, stream, data|
312
+ # if stream == :stderr
313
+ # puts "ERROR: #{data}"
314
+ # else
315
+ # puts data
316
+ # end
317
+ # end
318
+ def exec(command, &block)
319
+ open_channel do |channel|
320
+ channel.exec(command) do |ch, success|
321
+ raise "could not execute command: #{command.inspect}" unless success
322
+
323
+ channel.on_data do |ch2, data|
324
+ if block
325
+ block.call(ch2, :stdout, data)
326
+ else
327
+ $stdout.print(data)
328
+ end
329
+ end
330
+
331
+ channel.on_extended_data do |ch2, type, data|
332
+ if block
333
+ block.call(ch2, :stderr, data)
334
+ else
335
+ $stderr.print(data)
336
+ end
337
+ end
338
+ end
339
+ end
340
+ end
341
+
342
+ # Same as #exec, except this will block until the command finishes. Also,
343
+ # if a block is not given, this will return all output (stdout and stderr)
344
+ # as a single string.
345
+ #
346
+ # matches = ssh.exec!("grep something /some/files")
347
+ def exec!(command, &block)
348
+ block ||= Proc.new do |ch, type, data|
349
+ ch[:result] ||= ""
350
+ ch[:result] << data
351
+ end
352
+
353
+ channel = exec(command, &block)
354
+ channel.wait
355
+
356
+ return channel[:result]
357
+ end
358
+
359
+ # Enqueues a message to be sent to the server as soon as the socket is
360
+ # available for writing. Most programs will never need to call this, but
361
+ # if you are implementing an extension to the SSH protocol, or if you
362
+ # need to send a packet that Net::SSH does not directly support, you can
363
+ # use this to send it.
364
+ #
365
+ # ssh.send_message(Buffer.from(:byte, REQUEST_SUCCESS).to_s)
366
+ def send_message(message)
367
+ transport.enqueue_message(message)
368
+ end
369
+
370
+ # Adds an IO object for the event loop to listen to. If a callback
371
+ # is given, it will be invoked when the io is ready to be read, otherwise,
372
+ # the io will merely have its #fill method invoked.
373
+ #
374
+ # Any +io+ value passed to this method _must_ have mixed into it the
375
+ # Net::SSH::BufferedIo functionality, typically by calling #extend on the
376
+ # object.
377
+ #
378
+ # The following example executes a process on the remote server, opens
379
+ # a socket to somewhere, and then pipes data from that socket to the
380
+ # remote process' stdin stream:
381
+ #
382
+ # channel = ssh.open_channel do |ch|
383
+ # ch.exec "/some/process/that/wants/input" do |ch, success|
384
+ # abort "can't execute!" unless success
385
+ #
386
+ # io = TCPSocket.new(somewhere, port)
387
+ # io.extend(Net::SSH::BufferedIo)
388
+ # ssh.listen_to(io)
389
+ #
390
+ # ch.on_process do
391
+ # if io.available > 0
392
+ # ch.send_data(io.read_available)
393
+ # end
394
+ # end
395
+ #
396
+ # ch.on_close do
397
+ # ssh.stop_listening_to(io)
398
+ # io.close
399
+ # end
400
+ # end
401
+ # end
402
+ #
403
+ # channel.wait
404
+ def listen_to(io, &callback)
405
+ listeners[io] = callback
406
+ end
407
+
408
+ # Removes the given io object from the listeners collection, so that the
409
+ # event loop will no longer monitor it.
410
+ def stop_listening_to(io)
411
+ listeners.delete(io)
412
+ end
413
+
414
+ # Returns a reference to the Net::SSH::Service::Forward service, which can
415
+ # be used for forwarding ports over SSH.
416
+ def forward
417
+ @forward ||= Service::Forward.new(self)
418
+ end
419
+
420
+ # Registers a handler to be invoked when the server wants to open a
421
+ # channel on the client. The callback receives the connection object,
422
+ # the new channel object, and the packet itself as arguments, and should
423
+ # raise ChannelOpenFailed if it is unable to open the channel for some
424
+ # reason. Otherwise, the channel will be opened and a confirmation message
425
+ # sent to the server.
426
+ #
427
+ # This is used by the Net::SSH::Service::Forward service to open a channel
428
+ # when a remote forwarded port receives a connection. However, you are
429
+ # welcome to register handlers for other channel types, as needed.
430
+ def on_open_channel(type, &block)
431
+ channel_open_handlers[type] = block
432
+ end
433
+
434
+ # Registers a handler to be invoked when the server sends a global request
435
+ # of the given type. The callback receives the request data as the first
436
+ # parameter, and true/false as the second (indicating whether a response
437
+ # is required). If the callback sends the response, it should return
438
+ # :sent. Otherwise, if it returns true, REQUEST_SUCCESS will be sent, and
439
+ # if it returns false, REQUEST_FAILURE will be sent.
440
+ def on_global_request(type, &block)
441
+ old, @on_global_request[type] = @on_global_request[type], block
442
+ old
443
+ end
444
+
445
+ private
446
+
447
+ # Read all pending packets from the connection and dispatch them as
448
+ # appropriate. Returns as soon as there are no more pending packets.
449
+ def dispatch_incoming_packets
450
+ while packet = transport.poll_message
451
+ unless MAP.key?(packet.type)
452
+ raise Net::SSH::Exception, "unexpected response #{packet.type} (#{packet.inspect})"
453
+ end
454
+
455
+ send(MAP[packet.type], packet)
456
+ end
457
+ end
458
+
459
+ # Returns the next available channel id to be assigned, and increments
460
+ # the counter.
461
+ def get_next_channel_id
462
+ @channel_id_counter += 1
463
+ end
464
+
465
+ # Invoked when a global request is received. The registered global
466
+ # request callback will be invoked, if one exists, and the necessary
467
+ # reply returned.
468
+ def global_request(packet)
469
+ info { "global request received: #{packet[:request_type]} #{packet[:want_reply]}" }
470
+ callback = @on_global_request[packet[:request_type]]
471
+ result = callback ? callback.call(packet[:request_data], packet[:want_reply]) : false
472
+
473
+ if result != :sent && result != true && result != false
474
+ raise "expected global request handler for `#{packet[:request_type]}' to return true, false, or :sent, but got #{result.inspect}"
475
+ end
476
+
477
+ if packet[:want_reply] && result != :sent
478
+ msg = Buffer.from(:byte, result ? REQUEST_SUCCESS : REQUEST_FAILURE)
479
+ send_message(msg)
480
+ end
481
+ end
482
+
483
+ # Invokes the next pending request callback with +true+.
484
+ def request_success(packet)
485
+ info { "global request success" }
486
+ callback = pending_requests.shift
487
+ callback.call(true, packet) if callback
488
+ end
489
+
490
+ # Invokes the next pending request callback with +false+.
491
+ def request_failure(packet)
492
+ info { "global request failure" }
493
+ callback = pending_requests.shift
494
+ callback.call(false, packet) if callback
495
+ end
496
+
497
+ # Called when the server wants to open a channel. If no registered
498
+ # channel handler exists for the given channel type, CHANNEL_OPEN_FAILURE
499
+ # is returned, otherwise the callback is invoked and everything proceeds
500
+ # accordingly.
501
+ def channel_open(packet)
502
+ info { "channel open #{packet[:channel_type]}" }
503
+
504
+ local_id = get_next_channel_id
505
+ channel = Channel.new(self, packet[:channel_type], local_id)
506
+ channel.do_open_confirmation(packet[:remote_id], packet[:window_size], packet[:packet_size])
507
+
508
+ callback = channel_open_handlers[packet[:channel_type]]
509
+
510
+ if callback
511
+ begin
512
+ callback[self, channel, packet]
513
+ rescue ChannelOpenFailed => err
514
+ failure = [err.code, err.reason]
515
+ else
516
+ channels[local_id] = channel
517
+ msg = Buffer.from(:byte, CHANNEL_OPEN_CONFIRMATION, :long, channel.remote_id, :long, channel.local_id, :long, channel.local_maximum_window_size, :long, channel.local_maximum_packet_size)
518
+ end
519
+ else
520
+ failure = [3, "unknown channel type #{channel.type}"]
521
+ end
522
+
523
+ if failure
524
+ error { failure.inspect }
525
+ msg = Buffer.from(:byte, CHANNEL_OPEN_FAILURE, :long, channel.remote_id, :long, failure[0], :string, failure[1], :string, "")
526
+ end
527
+
528
+ send_message(msg)
529
+ end
530
+
531
+ def channel_open_confirmation(packet)
532
+ info { "channel_open_confirmation: #{packet[:local_id]} #{packet[:remote_id]} #{packet[:window_size]} #{packet[:packet_size]}" }
533
+ channel = channels[packet[:local_id]]
534
+ channel.do_open_confirmation(packet[:remote_id], packet[:window_size], packet[:packet_size])
535
+ end
536
+
537
+ def channel_open_failure(packet)
538
+ error { "channel_open_failed: #{packet[:local_id]} #{packet[:reason_code]} #{packet[:description]}" }
539
+ channel = channels.delete(packet[:local_id])
540
+ channel.do_open_failed(packet[:reason_code], packet[:description])
541
+ end
542
+
543
+ def channel_window_adjust(packet)
544
+ info { "channel_window_adjust: #{packet[:local_id]} +#{packet[:extra_bytes]}" }
545
+ channels[packet[:local_id]].do_window_adjust(packet[:extra_bytes])
546
+ end
547
+
548
+ def channel_request(packet)
549
+ info { "channel_request: #{packet[:local_id]} #{packet[:request]} #{packet[:want_reply]}" }
550
+ channels[packet[:local_id]].do_request(packet[:request], packet[:want_reply], packet[:request_data])
551
+ end
552
+
553
+ def channel_data(packet)
554
+ info { "channel_data: #{packet[:local_id]} #{packet[:data].length}b" }
555
+ channels[packet[:local_id]].do_data(packet[:data])
556
+ end
557
+
558
+ def channel_extended_data(packet)
559
+ info { "channel_extended_data: #{packet[:local_id]} #{packet[:data_type]} #{packet[:data].length}b" }
560
+ channels[packet[:local_id]].do_extended_data(packet[:data_type], packet[:data])
561
+ end
562
+
563
+ def channel_eof(packet)
564
+ info { "channel_eof: #{packet[:local_id]}" }
565
+ channels[packet[:local_id]].do_eof
566
+ end
567
+
568
+ def channel_close(packet)
569
+ info { "channel_close: #{packet[:local_id]}" }
570
+
571
+ channel = channels[packet[:local_id]]
572
+ channel.close
573
+
574
+ channels.delete(packet[:local_id])
575
+ channel.do_close
576
+ end
577
+
578
+ def channel_success(packet)
579
+ info { "channel_success: #{packet[:local_id]}" }
580
+ channels[packet[:local_id]].do_success
581
+ end
582
+
583
+ def channel_failure(packet)
584
+ info { "channel_failure: #{packet[:local_id]}" }
585
+ channels[packet[:local_id]].do_failure
586
+ end
587
+
588
+ MAP = Constants.constants.inject({}) do |memo, name|
589
+ value = const_get(name)
590
+ next unless Integer === value
591
+ memo[value] = name.downcase.to_sym
592
+ memo
593
+ end
594
+ end
595
+
596
+ end; end; end