ddollar-net-ssh 2.0.1

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