net-ssh 5.2.0 → 7.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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data/.dockerignore +6 -0
- data/.github/config/rubocop_linter_action.yml +4 -0
- data/.github/workflows/ci-with-docker.yml +44 -0
- data/.github/workflows/ci.yml +87 -0
- data/.github/workflows/rubocop.yml +13 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +16 -2
- data/.rubocop_todo.yml +623 -511
- data/CHANGES.txt +50 -2
- data/Dockerfile +27 -0
- data/Dockerfile.openssl3 +17 -0
- data/Gemfile +2 -0
- data/Gemfile.noed25519 +2 -0
- data/Manifest +0 -1
- data/README.md +293 -0
- data/Rakefile +6 -2
- data/appveyor.yml +4 -2
- data/docker-compose.yml +23 -0
- data/lib/net/ssh/authentication/agent.rb +29 -13
- data/lib/net/ssh/authentication/certificate.rb +19 -7
- data/lib/net/ssh/authentication/constants.rb +0 -1
- data/lib/net/ssh/authentication/ed25519.rb +13 -8
- data/lib/net/ssh/authentication/ed25519_loader.rb +5 -8
- data/lib/net/ssh/authentication/key_manager.rb +73 -32
- data/lib/net/ssh/authentication/methods/abstract.rb +12 -3
- data/lib/net/ssh/authentication/methods/hostbased.rb +3 -5
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +5 -3
- data/lib/net/ssh/authentication/methods/none.rb +6 -9
- data/lib/net/ssh/authentication/methods/password.rb +2 -3
- data/lib/net/ssh/authentication/methods/publickey.rb +56 -16
- data/lib/net/ssh/authentication/pageant.rb +97 -97
- data/lib/net/ssh/authentication/pub_key_fingerprint.rb +2 -3
- data/lib/net/ssh/authentication/session.rb +27 -23
- data/lib/net/ssh/buffer.rb +51 -40
- data/lib/net/ssh/buffered_io.rb +24 -26
- data/lib/net/ssh/config.rb +82 -50
- data/lib/net/ssh/connection/channel.rb +101 -87
- data/lib/net/ssh/connection/constants.rb +0 -4
- data/lib/net/ssh/connection/event_loop.rb +30 -25
- data/lib/net/ssh/connection/keepalive.rb +12 -12
- data/lib/net/ssh/connection/session.rb +115 -111
- data/lib/net/ssh/connection/term.rb +56 -58
- data/lib/net/ssh/errors.rb +12 -12
- data/lib/net/ssh/key_factory.rb +10 -13
- data/lib/net/ssh/known_hosts.rb +106 -39
- data/lib/net/ssh/loggable.rb +10 -11
- data/lib/net/ssh/packet.rb +1 -1
- data/lib/net/ssh/prompt.rb +9 -11
- data/lib/net/ssh/proxy/command.rb +1 -2
- data/lib/net/ssh/proxy/errors.rb +2 -4
- data/lib/net/ssh/proxy/http.rb +18 -20
- data/lib/net/ssh/proxy/https.rb +8 -10
- data/lib/net/ssh/proxy/jump.rb +8 -10
- data/lib/net/ssh/proxy/socks4.rb +2 -4
- data/lib/net/ssh/proxy/socks5.rb +3 -6
- data/lib/net/ssh/service/forward.rb +9 -8
- data/lib/net/ssh/test/channel.rb +24 -26
- data/lib/net/ssh/test/extensions.rb +35 -35
- data/lib/net/ssh/test/kex.rb +6 -8
- data/lib/net/ssh/test/local_packet.rb +0 -2
- data/lib/net/ssh/test/packet.rb +3 -3
- data/lib/net/ssh/test/remote_packet.rb +6 -8
- data/lib/net/ssh/test/script.rb +25 -27
- data/lib/net/ssh/test/socket.rb +12 -15
- data/lib/net/ssh/test.rb +7 -7
- data/lib/net/ssh/transport/algorithms.rb +100 -58
- data/lib/net/ssh/transport/cipher_factory.rb +34 -50
- data/lib/net/ssh/transport/constants.rb +13 -9
- data/lib/net/ssh/transport/ctr.rb +8 -14
- data/lib/net/ssh/transport/hmac/abstract.rb +20 -5
- data/lib/net/ssh/transport/hmac/md5.rb +0 -2
- data/lib/net/ssh/transport/hmac/md5_96.rb +0 -2
- data/lib/net/ssh/transport/hmac/none.rb +0 -2
- data/lib/net/ssh/transport/hmac/ripemd160.rb +0 -2
- data/lib/net/ssh/transport/hmac/sha1.rb +0 -2
- data/lib/net/ssh/transport/hmac/sha1_96.rb +0 -2
- data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
- data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
- data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
- data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
- data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac.rb +13 -11
- data/lib/net/ssh/transport/identity_cipher.rb +11 -13
- data/lib/net/ssh/transport/kex/abstract.rb +130 -0
- data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +5 -19
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +30 -139
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +1 -8
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +20 -81
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +5 -4
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +5 -4
- data/lib/net/ssh/transport/kex.rb +15 -10
- data/lib/net/ssh/transport/key_expander.rb +7 -8
- data/lib/net/ssh/transport/openssl.rb +149 -127
- data/lib/net/ssh/transport/packet_stream.rb +50 -16
- data/lib/net/ssh/transport/server_version.rb +17 -16
- data/lib/net/ssh/transport/session.rb +9 -7
- data/lib/net/ssh/transport/state.rb +44 -44
- data/lib/net/ssh/verifiers/accept_new.rb +0 -2
- data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +1 -2
- data/lib/net/ssh/verifiers/always.rb +6 -4
- data/lib/net/ssh/verifiers/never.rb +0 -2
- data/lib/net/ssh/version.rb +3 -3
- data/lib/net/ssh.rb +12 -8
- data/net-ssh-public_cert.pem +8 -8
- data/net-ssh.gemspec +9 -7
- data/support/ssh_tunnel_bug.rb +3 -3
- data.tar.gz.sig +0 -0
- metadata +55 -30
- metadata.gz.sig +0 -0
- data/.travis.yml +0 -53
- data/Gemfile.noed25519.lock +0 -41
- data/README.rdoc +0 -194
- data/lib/net/ssh/ruby_compat.rb +0 -13
- data/support/arcfour_check.rb +0 -20
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
require 'net/ssh/loggable'
|
|
2
|
-
require 'net/ssh/ruby_compat'
|
|
3
2
|
require 'net/ssh/connection/channel'
|
|
4
3
|
require 'net/ssh/connection/constants'
|
|
5
4
|
require 'net/ssh/service/forward'
|
|
6
5
|
require 'net/ssh/connection/keepalive'
|
|
7
6
|
require 'net/ssh/connection/event_loop'
|
|
8
7
|
|
|
9
|
-
module Net
|
|
10
|
-
module SSH
|
|
8
|
+
module Net
|
|
9
|
+
module SSH
|
|
11
10
|
module Connection
|
|
12
|
-
|
|
13
11
|
# A session class representing the connection service running on top of
|
|
14
12
|
# the SSH transport layer. It manages the creation of channels (see
|
|
15
13
|
# #open_channel), and the dispatching of messages to the various channels.
|
|
@@ -29,50 +27,50 @@ module Net
|
|
|
29
27
|
class Session
|
|
30
28
|
include Loggable
|
|
31
29
|
include Constants
|
|
32
|
-
|
|
30
|
+
|
|
33
31
|
# Default IO.select timeout threshold
|
|
34
32
|
DEFAULT_IO_SELECT_TIMEOUT = 300
|
|
35
|
-
|
|
33
|
+
|
|
36
34
|
# The underlying transport layer abstraction (see Net::SSH::Transport::Session).
|
|
37
35
|
attr_reader :transport
|
|
38
|
-
|
|
36
|
+
|
|
39
37
|
# The map of options that were used to initialize this instance.
|
|
40
38
|
attr_reader :options
|
|
41
|
-
|
|
39
|
+
|
|
42
40
|
# The collection of custom properties for this instance. (See #[] and #[]=).
|
|
43
41
|
attr_reader :properties
|
|
44
|
-
|
|
42
|
+
|
|
45
43
|
# The map of channels, each key being the local-id for the channel.
|
|
46
|
-
attr_reader :channels
|
|
47
|
-
|
|
44
|
+
attr_reader :channels # :nodoc:
|
|
45
|
+
|
|
48
46
|
# The map of listeners that the event loop knows about. See #listen_to.
|
|
49
|
-
attr_reader :listeners
|
|
50
|
-
|
|
47
|
+
attr_reader :listeners # :nodoc:
|
|
48
|
+
|
|
51
49
|
# The map of specialized handlers for opening specific channel types. See
|
|
52
50
|
# #on_open_channel.
|
|
53
|
-
attr_reader :channel_open_handlers
|
|
54
|
-
|
|
51
|
+
attr_reader :channel_open_handlers # :nodoc:
|
|
52
|
+
|
|
55
53
|
# The list of callbacks for pending requests. See #send_global_request.
|
|
56
|
-
attr_reader :pending_requests
|
|
57
|
-
|
|
54
|
+
attr_reader :pending_requests # :nodoc:
|
|
55
|
+
|
|
58
56
|
class NilChannel
|
|
59
57
|
def initialize(session)
|
|
60
58
|
@session = session
|
|
61
59
|
end
|
|
62
|
-
|
|
60
|
+
|
|
63
61
|
def method_missing(sym, *args)
|
|
64
62
|
@session.lwarn { "ignoring request #{sym.inspect} for non-existent (closed?) channel; probably ssh server bug" }
|
|
65
63
|
end
|
|
66
64
|
end
|
|
67
|
-
|
|
65
|
+
|
|
68
66
|
# Create a new connection service instance atop the given transport
|
|
69
67
|
# layer. Initializes the listeners to be only the underlying socket object.
|
|
70
|
-
def initialize(transport, options={})
|
|
68
|
+
def initialize(transport, options = {})
|
|
71
69
|
self.logger = transport.logger
|
|
72
|
-
|
|
70
|
+
|
|
73
71
|
@transport = transport
|
|
74
72
|
@options = options
|
|
75
|
-
|
|
73
|
+
|
|
76
74
|
@channel_id_counter = -1
|
|
77
75
|
@channels = Hash.new(NilChannel.new(self))
|
|
78
76
|
@listeners = { transport.socket => nil }
|
|
@@ -80,34 +78,34 @@ module Net
|
|
|
80
78
|
@channel_open_handlers = {}
|
|
81
79
|
@on_global_request = {}
|
|
82
80
|
@properties = (options[:properties] || {}).dup
|
|
83
|
-
|
|
81
|
+
|
|
84
82
|
@max_pkt_size = (options.key?(:max_pkt_size) ? options[:max_pkt_size] : 0x8000)
|
|
85
83
|
@max_win_size = (options.key?(:max_win_size) ? options[:max_win_size] : 0x20000)
|
|
86
|
-
|
|
84
|
+
|
|
87
85
|
@keepalive = Keepalive.new(self)
|
|
88
|
-
|
|
86
|
+
|
|
89
87
|
@event_loop = options[:event_loop] || SingleSessionEventLoop.new
|
|
90
88
|
@event_loop.register(self)
|
|
91
89
|
end
|
|
92
|
-
|
|
90
|
+
|
|
93
91
|
# Retrieves a custom property from this instance. This can be used to
|
|
94
92
|
# store additional state in applications that must manage multiple
|
|
95
93
|
# SSH connections.
|
|
96
94
|
def [](key)
|
|
97
95
|
@properties[key]
|
|
98
96
|
end
|
|
99
|
-
|
|
97
|
+
|
|
100
98
|
# Sets a custom property for this instance.
|
|
101
99
|
def []=(key, value)
|
|
102
100
|
@properties[key] = value
|
|
103
101
|
end
|
|
104
|
-
|
|
102
|
+
|
|
105
103
|
# Returns the name of the host that was given to the transport layer to
|
|
106
104
|
# connect to.
|
|
107
105
|
def host
|
|
108
106
|
transport.host
|
|
109
107
|
end
|
|
110
|
-
|
|
108
|
+
|
|
111
109
|
# Returns true if the underlying transport has been closed. Note that
|
|
112
110
|
# this can be a little misleading, since if the remote server has
|
|
113
111
|
# closed the connection, the local end will still think it is open
|
|
@@ -116,7 +114,7 @@ module Net
|
|
|
116
114
|
def closed?
|
|
117
115
|
transport.closed?
|
|
118
116
|
end
|
|
119
|
-
|
|
117
|
+
|
|
120
118
|
# Closes the session gracefully, blocking until all channels have
|
|
121
119
|
# successfully closed, and then closes the underlying transport layer
|
|
122
120
|
# connection.
|
|
@@ -130,7 +128,7 @@ module Net
|
|
|
130
128
|
end
|
|
131
129
|
transport.close
|
|
132
130
|
end
|
|
133
|
-
|
|
131
|
+
|
|
134
132
|
# Performs a "hard" shutdown of the connection. In general, this should
|
|
135
133
|
# never be done, but it might be necessary (in a rescue clause, for instance,
|
|
136
134
|
# when the connection needs to close but you don't know the status of the
|
|
@@ -138,10 +136,10 @@ module Net
|
|
|
138
136
|
def shutdown!
|
|
139
137
|
transport.shutdown!
|
|
140
138
|
end
|
|
141
|
-
|
|
139
|
+
|
|
142
140
|
# preserve a reference to Kernel#loop
|
|
143
141
|
alias :loop_forever :loop
|
|
144
|
-
|
|
142
|
+
|
|
145
143
|
# Returns +true+ if there are any channels currently active on this
|
|
146
144
|
# session. By default, this will not include "invisible" channels
|
|
147
145
|
# (such as those created by forwarding ports and such), but if you pass
|
|
@@ -151,14 +149,14 @@ module Net
|
|
|
151
149
|
# to be run.
|
|
152
150
|
#
|
|
153
151
|
# ssh.loop { ssh.busy? }
|
|
154
|
-
def busy?(include_invisible=false)
|
|
152
|
+
def busy?(include_invisible = false)
|
|
155
153
|
if include_invisible
|
|
156
154
|
channels.any?
|
|
157
155
|
else
|
|
158
156
|
channels.any? { |id, ch| !ch[:invisible] }
|
|
159
157
|
end
|
|
160
158
|
end
|
|
161
|
-
|
|
159
|
+
|
|
162
160
|
# The main event loop. Calls #process until #process returns false. If a
|
|
163
161
|
# block is given, it is passed to #process, otherwise a default proc is
|
|
164
162
|
# used that just returns true if there are any channels active (see #busy?).
|
|
@@ -176,7 +174,7 @@ module Net
|
|
|
176
174
|
# int_pressed = false
|
|
177
175
|
# trap("INT") { int_pressed = true }
|
|
178
176
|
# ssh.loop(0.1) { not int_pressed }
|
|
179
|
-
def loop(wait=nil, &block)
|
|
177
|
+
def loop(wait = nil, &block)
|
|
180
178
|
running = block || Proc.new { busy? }
|
|
181
179
|
loop_forever { break unless process(wait, &running) }
|
|
182
180
|
begin
|
|
@@ -189,7 +187,7 @@ module Net
|
|
|
189
187
|
end
|
|
190
188
|
end
|
|
191
189
|
end
|
|
192
|
-
|
|
190
|
+
|
|
193
191
|
# The core of the event loop. It processes a single iteration of the event
|
|
194
192
|
# loop. If a block is given, it should return false when the processing
|
|
195
193
|
# should abort, which causes #process to return false. Otherwise,
|
|
@@ -224,13 +222,13 @@ module Net
|
|
|
224
222
|
# connections.delete_if { |ssh| !ssh.process(0.1, &condition) }
|
|
225
223
|
# break if connections.empty?
|
|
226
224
|
# end
|
|
227
|
-
def process(wait=nil, &block)
|
|
225
|
+
def process(wait = nil, &block)
|
|
228
226
|
@event_loop.process(wait, &block)
|
|
229
227
|
rescue StandardError
|
|
230
228
|
force_channel_cleanup_on_close if closed?
|
|
231
229
|
raise
|
|
232
230
|
end
|
|
233
|
-
|
|
231
|
+
|
|
234
232
|
# This is called internally as part of #process. It dispatches any
|
|
235
233
|
# available incoming packets, and then runs Net::SSH::Connection::Channel#process
|
|
236
234
|
# for any active channels. If a block is given, it is invoked at the
|
|
@@ -238,31 +236,33 @@ module Net
|
|
|
238
236
|
# false, this method returns false. Otherwise, it returns true.
|
|
239
237
|
def preprocess(&block)
|
|
240
238
|
return false if block_given? && !yield(self)
|
|
239
|
+
|
|
241
240
|
ev_preprocess(&block)
|
|
242
241
|
return false if block_given? && !yield(self)
|
|
242
|
+
|
|
243
243
|
return true
|
|
244
244
|
end
|
|
245
|
-
|
|
245
|
+
|
|
246
246
|
# Called by event loop to process available data before going to
|
|
247
247
|
# event multiplexing
|
|
248
248
|
def ev_preprocess(&block)
|
|
249
249
|
dispatch_incoming_packets(raise_disconnect_errors: false)
|
|
250
250
|
each_channel { |id, channel| channel.process unless channel.local_closed? }
|
|
251
251
|
end
|
|
252
|
-
|
|
252
|
+
|
|
253
253
|
# Returns the file descriptors the event loop should wait for read/write events,
|
|
254
254
|
# we also return the max wait
|
|
255
255
|
def ev_do_calculate_rw_wait(wait)
|
|
256
256
|
r = listeners.keys
|
|
257
257
|
w = r.select { |w2| w2.respond_to?(:pending_write?) && w2.pending_write? }
|
|
258
|
-
[r,w,io_select_wait(wait)]
|
|
258
|
+
[r, w, io_select_wait(wait)]
|
|
259
259
|
end
|
|
260
|
-
|
|
260
|
+
|
|
261
261
|
# This is called internally as part of #process.
|
|
262
262
|
def postprocess(readers, writers)
|
|
263
263
|
ev_do_handle_events(readers, writers)
|
|
264
264
|
end
|
|
265
|
-
|
|
265
|
+
|
|
266
266
|
# It loops over the given arrays of reader IO's and writer IO's,
|
|
267
267
|
# processing them as needed, and
|
|
268
268
|
# then calls Net::SSH::Transport::Session#rekey_as_needed to allow the
|
|
@@ -278,12 +278,12 @@ module Net
|
|
|
278
278
|
end
|
|
279
279
|
end
|
|
280
280
|
end
|
|
281
|
-
|
|
281
|
+
|
|
282
282
|
Array(writers).each do |writer|
|
|
283
283
|
writer.send_pending
|
|
284
284
|
end
|
|
285
285
|
end
|
|
286
|
-
|
|
286
|
+
|
|
287
287
|
# calls Net::SSH::Transport::Session#rekey_as_needed to allow the
|
|
288
288
|
# transport layer to rekey
|
|
289
289
|
def ev_do_postprocess(was_events)
|
|
@@ -291,7 +291,7 @@ module Net
|
|
|
291
291
|
transport.rekey_as_needed
|
|
292
292
|
true
|
|
293
293
|
end
|
|
294
|
-
|
|
294
|
+
|
|
295
295
|
# Send a global request of the given type. The +extra+ parameters must
|
|
296
296
|
# be even in number, and conform to the same format as described for
|
|
297
297
|
# Net::SSH::Buffer.from. If a callback is not specified, the request will
|
|
@@ -315,7 +315,7 @@ module Net
|
|
|
315
315
|
pending_requests << callback if callback
|
|
316
316
|
self
|
|
317
317
|
end
|
|
318
|
-
|
|
318
|
+
|
|
319
319
|
# Requests that a new channel be opened. By default, the channel will be
|
|
320
320
|
# of type "session", but if you know what you're doing you can select any
|
|
321
321
|
# of the channel types supported by the SSH protocol. The +extra+ parameters
|
|
@@ -335,27 +335,27 @@ module Net
|
|
|
335
335
|
# end
|
|
336
336
|
#
|
|
337
337
|
# channel.wait
|
|
338
|
-
def open_channel(type="session", *extra, &on_confirm)
|
|
338
|
+
def open_channel(type = "session", *extra, &on_confirm)
|
|
339
339
|
local_id = get_next_channel_id
|
|
340
|
-
|
|
340
|
+
|
|
341
341
|
channel = Channel.new(self, type, local_id, @max_pkt_size, @max_win_size, &on_confirm)
|
|
342
342
|
msg = Buffer.from(:byte, CHANNEL_OPEN, :string, type, :long, local_id,
|
|
343
|
-
|
|
344
|
-
|
|
343
|
+
:long, channel.local_maximum_window_size,
|
|
344
|
+
:long, channel.local_maximum_packet_size, *extra)
|
|
345
345
|
send_message(msg)
|
|
346
|
-
|
|
346
|
+
|
|
347
347
|
channels[local_id] = channel
|
|
348
348
|
end
|
|
349
|
-
|
|
349
|
+
|
|
350
350
|
class StringWithExitstatus < String
|
|
351
351
|
def initialize(str, exitstatus)
|
|
352
352
|
super(str)
|
|
353
353
|
@exitstatus = exitstatus
|
|
354
354
|
end
|
|
355
|
-
|
|
355
|
+
|
|
356
356
|
attr_reader :exitstatus
|
|
357
357
|
end
|
|
358
|
-
|
|
358
|
+
|
|
359
359
|
# A convenience method for executing a command and interacting with it. If
|
|
360
360
|
# no block is given, all output is printed via $stdout and $stderr. Otherwise,
|
|
361
361
|
# the block is called for each data and extended data packet, with three
|
|
@@ -380,17 +380,17 @@ module Net
|
|
|
380
380
|
open_channel do |channel|
|
|
381
381
|
channel.exec(command) do |ch, success|
|
|
382
382
|
raise "could not execute command: #{command.inspect}" unless success
|
|
383
|
-
|
|
383
|
+
|
|
384
384
|
if status
|
|
385
|
-
channel.on_request("exit-status") do |ch2,data|
|
|
385
|
+
channel.on_request("exit-status") do |ch2, data|
|
|
386
386
|
status[:exit_code] = data.read_long
|
|
387
387
|
end
|
|
388
|
-
|
|
388
|
+
|
|
389
389
|
channel.on_request("exit-signal") do |ch2, data|
|
|
390
390
|
status[:exit_signal] = data.read_long
|
|
391
391
|
end
|
|
392
392
|
end
|
|
393
|
-
|
|
393
|
+
|
|
394
394
|
channel.on_data do |ch2, data|
|
|
395
395
|
if block
|
|
396
396
|
block.call(ch2, :stdout, data)
|
|
@@ -398,7 +398,7 @@ module Net
|
|
|
398
398
|
$stdout.print(data)
|
|
399
399
|
end
|
|
400
400
|
end
|
|
401
|
-
|
|
401
|
+
|
|
402
402
|
channel.on_extended_data do |ch2, type, data|
|
|
403
403
|
if block
|
|
404
404
|
block.call(ch2, :stderr, data)
|
|
@@ -409,7 +409,7 @@ module Net
|
|
|
409
409
|
end
|
|
410
410
|
end
|
|
411
411
|
end
|
|
412
|
-
|
|
412
|
+
|
|
413
413
|
# Same as #exec, except this will block until the command finishes. Also,
|
|
414
414
|
# if no block is given, this will return all output (stdout and stderr)
|
|
415
415
|
# as a single string.
|
|
@@ -419,20 +419,20 @@ module Net
|
|
|
419
419
|
# the returned string has an exitstatus method to query it's exit satus
|
|
420
420
|
def exec!(command, status: nil, &block)
|
|
421
421
|
block_or_concat = block || Proc.new do |ch, type, data|
|
|
422
|
-
ch[:result] ||=
|
|
422
|
+
ch[:result] ||= String.new
|
|
423
423
|
ch[:result] << data
|
|
424
424
|
end
|
|
425
|
-
|
|
425
|
+
|
|
426
426
|
status ||= {}
|
|
427
427
|
channel = exec(command, status: status, &block_or_concat)
|
|
428
428
|
channel.wait
|
|
429
|
-
|
|
430
|
-
channel[:result] ||=
|
|
429
|
+
|
|
430
|
+
channel[:result] ||= String.new unless block
|
|
431
431
|
channel[:result] &&= channel[:result].force_encoding("UTF-8") unless block
|
|
432
|
-
|
|
432
|
+
|
|
433
433
|
StringWithExitstatus.new(channel[:result], status[:exit_code]) if channel[:result]
|
|
434
434
|
end
|
|
435
|
-
|
|
435
|
+
|
|
436
436
|
# Enqueues a message to be sent to the server as soon as the socket is
|
|
437
437
|
# available for writing. Most programs will never need to call this, but
|
|
438
438
|
# if you are implementing an extension to the SSH protocol, or if you
|
|
@@ -443,7 +443,7 @@ module Net
|
|
|
443
443
|
def send_message(message)
|
|
444
444
|
transport.enqueue_message(message)
|
|
445
445
|
end
|
|
446
|
-
|
|
446
|
+
|
|
447
447
|
# Adds an IO object for the event loop to listen to. If a callback
|
|
448
448
|
# is given, it will be invoked when the io is ready to be read, otherwise,
|
|
449
449
|
# the io will merely have its #fill method invoked.
|
|
@@ -481,19 +481,19 @@ module Net
|
|
|
481
481
|
def listen_to(io, &callback)
|
|
482
482
|
listeners[io] = callback
|
|
483
483
|
end
|
|
484
|
-
|
|
484
|
+
|
|
485
485
|
# Removes the given io object from the listeners collection, so that the
|
|
486
486
|
# event loop will no longer monitor it.
|
|
487
487
|
def stop_listening_to(io)
|
|
488
488
|
listeners.delete(io)
|
|
489
489
|
end
|
|
490
|
-
|
|
490
|
+
|
|
491
491
|
# Returns a reference to the Net::SSH::Service::Forward service, which can
|
|
492
492
|
# be used for forwarding ports over SSH.
|
|
493
493
|
def forward
|
|
494
494
|
@forward ||= Service::Forward.new(self)
|
|
495
495
|
end
|
|
496
|
-
|
|
496
|
+
|
|
497
497
|
# Registers a handler to be invoked when the server wants to open a
|
|
498
498
|
# channel on the client. The callback receives the connection object,
|
|
499
499
|
# the new channel object, and the packet itself as arguments, and should
|
|
@@ -507,7 +507,7 @@ module Net
|
|
|
507
507
|
def on_open_channel(type, &block)
|
|
508
508
|
channel_open_handlers[type] = block
|
|
509
509
|
end
|
|
510
|
-
|
|
510
|
+
|
|
511
511
|
# Registers a handler to be invoked when the server sends a global request
|
|
512
512
|
# of the given type. The callback receives the request data as the first
|
|
513
513
|
# parameter, and true/false as the second (indicating whether a response
|
|
@@ -518,61 +518,61 @@ module Net
|
|
|
518
518
|
old, @on_global_request[type] = @on_global_request[type], block
|
|
519
519
|
old
|
|
520
520
|
end
|
|
521
|
-
|
|
521
|
+
|
|
522
522
|
def cleanup_channel(channel)
|
|
523
523
|
if channel.local_closed? and channel.remote_closed?
|
|
524
524
|
info { "#{host} delete channel #{channel.local_id} which closed locally and remotely" }
|
|
525
525
|
channels.delete(channel.local_id)
|
|
526
526
|
end
|
|
527
527
|
end
|
|
528
|
-
|
|
528
|
+
|
|
529
529
|
# If the #preprocess and #postprocess callbacks for this session need to run
|
|
530
530
|
# periodically, this method returns the maximum number of seconds which may
|
|
531
531
|
# pass between callbacks.
|
|
532
532
|
def max_select_wait_time
|
|
533
533
|
@keepalive.interval if @keepalive.enabled?
|
|
534
534
|
end
|
|
535
|
-
|
|
535
|
+
|
|
536
536
|
private
|
|
537
|
-
|
|
537
|
+
|
|
538
538
|
# iterate channels with the posibility of callbacks opening new channels during the iteration
|
|
539
539
|
def each_channel(&block)
|
|
540
540
|
channels.dup.each(&block)
|
|
541
541
|
end
|
|
542
|
-
|
|
542
|
+
|
|
543
543
|
# Read all pending packets from the connection and dispatch them as
|
|
544
544
|
# appropriate. Returns as soon as there are no more pending packets.
|
|
545
545
|
def dispatch_incoming_packets(raise_disconnect_errors: true)
|
|
546
546
|
while packet = transport.poll_message
|
|
547
547
|
raise Net::SSH::Exception, "unexpected response #{packet.type} (#{packet.inspect})" unless MAP.key?(packet.type)
|
|
548
|
-
|
|
548
|
+
|
|
549
549
|
send(MAP[packet.type], packet)
|
|
550
550
|
end
|
|
551
551
|
rescue StandardError
|
|
552
552
|
force_channel_cleanup_on_close if closed?
|
|
553
553
|
raise if raise_disconnect_errors || !$!.is_a?(Net::SSH::Disconnect)
|
|
554
554
|
end
|
|
555
|
-
|
|
555
|
+
|
|
556
556
|
# Returns the next available channel id to be assigned, and increments
|
|
557
557
|
# the counter.
|
|
558
558
|
def get_next_channel_id
|
|
559
559
|
@channel_id_counter += 1
|
|
560
560
|
end
|
|
561
|
-
|
|
561
|
+
|
|
562
562
|
def force_channel_cleanup_on_close
|
|
563
563
|
channels.each do |id, channel|
|
|
564
564
|
channel_closed(channel)
|
|
565
565
|
end
|
|
566
566
|
end
|
|
567
|
-
|
|
567
|
+
|
|
568
568
|
def channel_closed(channel)
|
|
569
569
|
channel.remote_closed!
|
|
570
570
|
channel.close
|
|
571
|
-
|
|
571
|
+
|
|
572
572
|
cleanup_channel(channel)
|
|
573
573
|
channel.do_close
|
|
574
574
|
end
|
|
575
|
-
|
|
575
|
+
|
|
576
576
|
# Invoked when a global request is received. The registered global
|
|
577
577
|
# request callback will be invoked, if one exists, and the necessary
|
|
578
578
|
# reply returned.
|
|
@@ -580,43 +580,45 @@ module Net
|
|
|
580
580
|
info { "global request received: #{packet[:request_type]} #{packet[:want_reply]}" }
|
|
581
581
|
callback = @on_global_request[packet[:request_type]]
|
|
582
582
|
result = callback ? callback.call(packet[:request_data], packet[:want_reply]) : false
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
583
|
+
|
|
584
|
+
if result != :sent && result != true && result != false
|
|
585
|
+
raise "expected global request handler for `#{packet[:request_type]}' to return true, false, or :sent, but got #{result.inspect}"
|
|
586
|
+
end
|
|
587
|
+
|
|
586
588
|
if packet[:want_reply] && result != :sent
|
|
587
589
|
msg = Buffer.from(:byte, result ? REQUEST_SUCCESS : REQUEST_FAILURE)
|
|
588
590
|
send_message(msg)
|
|
589
591
|
end
|
|
590
592
|
end
|
|
591
|
-
|
|
593
|
+
|
|
592
594
|
# Invokes the next pending request callback with +true+.
|
|
593
595
|
def request_success(packet)
|
|
594
596
|
info { "global request success" }
|
|
595
597
|
callback = pending_requests.shift
|
|
596
598
|
callback.call(true, packet) if callback
|
|
597
599
|
end
|
|
598
|
-
|
|
600
|
+
|
|
599
601
|
# Invokes the next pending request callback with +false+.
|
|
600
602
|
def request_failure(packet)
|
|
601
603
|
info { "global request failure" }
|
|
602
604
|
callback = pending_requests.shift
|
|
603
605
|
callback.call(false, packet) if callback
|
|
604
606
|
end
|
|
605
|
-
|
|
607
|
+
|
|
606
608
|
# Called when the server wants to open a channel. If no registered
|
|
607
609
|
# channel handler exists for the given channel type, CHANNEL_OPEN_FAILURE
|
|
608
610
|
# is returned, otherwise the callback is invoked and everything proceeds
|
|
609
611
|
# accordingly.
|
|
610
612
|
def channel_open(packet)
|
|
611
613
|
info { "channel open #{packet[:channel_type]}" }
|
|
612
|
-
|
|
614
|
+
|
|
613
615
|
local_id = get_next_channel_id
|
|
614
|
-
|
|
616
|
+
|
|
615
617
|
channel = Channel.new(self, packet[:channel_type], local_id, @max_pkt_size, @max_win_size)
|
|
616
618
|
channel.do_open_confirmation(packet[:remote_id], packet[:window_size], packet[:packet_size])
|
|
617
|
-
|
|
619
|
+
|
|
618
620
|
callback = channel_open_handlers[packet[:channel_type]]
|
|
619
|
-
|
|
621
|
+
|
|
620
622
|
if callback
|
|
621
623
|
begin
|
|
622
624
|
callback[self, channel, packet]
|
|
@@ -624,85 +626,87 @@ module Net
|
|
|
624
626
|
failure = [err.code, err.reason]
|
|
625
627
|
else
|
|
626
628
|
channels[local_id] = channel
|
|
627
|
-
msg = Buffer.from(:byte, CHANNEL_OPEN_CONFIRMATION, :long, channel.remote_id, :long,
|
|
629
|
+
msg = Buffer.from(:byte, CHANNEL_OPEN_CONFIRMATION, :long, channel.remote_id, :long,
|
|
630
|
+
channel.local_id, :long, channel.local_maximum_window_size, :long,
|
|
631
|
+
channel.local_maximum_packet_size)
|
|
628
632
|
end
|
|
629
633
|
else
|
|
630
634
|
failure = [3, "unknown channel type #{channel.type}"]
|
|
631
635
|
end
|
|
632
|
-
|
|
636
|
+
|
|
633
637
|
if failure
|
|
634
638
|
error { failure.inspect }
|
|
635
639
|
msg = Buffer.from(:byte, CHANNEL_OPEN_FAILURE, :long, channel.remote_id, :long, failure[0], :string, failure[1], :string, "")
|
|
636
640
|
end
|
|
637
|
-
|
|
641
|
+
|
|
638
642
|
send_message(msg)
|
|
639
643
|
end
|
|
640
|
-
|
|
644
|
+
|
|
641
645
|
def channel_open_confirmation(packet)
|
|
642
646
|
info { "channel_open_confirmation: #{packet[:local_id]} #{packet[:remote_id]} #{packet[:window_size]} #{packet[:packet_size]}" }
|
|
643
647
|
channel = channels[packet[:local_id]]
|
|
644
648
|
channel.do_open_confirmation(packet[:remote_id], packet[:window_size], packet[:packet_size])
|
|
645
649
|
end
|
|
646
|
-
|
|
650
|
+
|
|
647
651
|
def channel_open_failure(packet)
|
|
648
652
|
error { "channel_open_failed: #{packet[:local_id]} #{packet[:reason_code]} #{packet[:description]}" }
|
|
649
653
|
channel = channels.delete(packet[:local_id])
|
|
650
654
|
channel.do_open_failed(packet[:reason_code], packet[:description])
|
|
651
655
|
end
|
|
652
|
-
|
|
656
|
+
|
|
653
657
|
def channel_window_adjust(packet)
|
|
654
658
|
info { "channel_window_adjust: #{packet[:local_id]} +#{packet[:extra_bytes]}" }
|
|
655
659
|
channels[packet[:local_id]].do_window_adjust(packet[:extra_bytes])
|
|
656
660
|
end
|
|
657
|
-
|
|
661
|
+
|
|
658
662
|
def channel_request(packet)
|
|
659
663
|
info { "channel_request: #{packet[:local_id]} #{packet[:request]} #{packet[:want_reply]}" }
|
|
660
664
|
channels[packet[:local_id]].do_request(packet[:request], packet[:want_reply], packet[:request_data])
|
|
661
665
|
end
|
|
662
|
-
|
|
666
|
+
|
|
663
667
|
def channel_data(packet)
|
|
664
668
|
info { "channel_data: #{packet[:local_id]} #{packet[:data].length}b" }
|
|
665
669
|
channels[packet[:local_id]].do_data(packet[:data])
|
|
666
670
|
end
|
|
667
|
-
|
|
671
|
+
|
|
668
672
|
def channel_extended_data(packet)
|
|
669
673
|
info { "channel_extended_data: #{packet[:local_id]} #{packet[:data_type]} #{packet[:data].length}b" }
|
|
670
674
|
channels[packet[:local_id]].do_extended_data(packet[:data_type], packet[:data])
|
|
671
675
|
end
|
|
672
|
-
|
|
676
|
+
|
|
673
677
|
def channel_eof(packet)
|
|
674
678
|
info { "channel_eof: #{packet[:local_id]}" }
|
|
675
679
|
channels[packet[:local_id]].do_eof
|
|
676
680
|
end
|
|
677
|
-
|
|
681
|
+
|
|
678
682
|
def channel_close(packet)
|
|
679
683
|
info { "channel_close: #{packet[:local_id]}" }
|
|
680
|
-
|
|
684
|
+
|
|
681
685
|
channel = channels[packet[:local_id]]
|
|
682
686
|
channel_closed(channel)
|
|
683
687
|
end
|
|
684
|
-
|
|
688
|
+
|
|
685
689
|
def channel_success(packet)
|
|
686
690
|
info { "channel_success: #{packet[:local_id]}" }
|
|
687
691
|
channels[packet[:local_id]].do_success
|
|
688
692
|
end
|
|
689
|
-
|
|
693
|
+
|
|
690
694
|
def channel_failure(packet)
|
|
691
695
|
info { "channel_failure: #{packet[:local_id]}" }
|
|
692
696
|
channels[packet[:local_id]].do_failure
|
|
693
697
|
end
|
|
694
|
-
|
|
698
|
+
|
|
695
699
|
def io_select_wait(wait)
|
|
696
700
|
[wait, max_select_wait_time].compact.min
|
|
697
701
|
end
|
|
698
|
-
|
|
702
|
+
|
|
699
703
|
MAP = Constants.constants.each_with_object({}) do |name, memo|
|
|
700
704
|
value = const_get(name)
|
|
701
705
|
next unless Integer === value
|
|
706
|
+
|
|
702
707
|
memo[value] = name.downcase.to_sym
|
|
703
708
|
end
|
|
704
709
|
end
|
|
705
|
-
|
|
706
710
|
end
|
|
707
711
|
end
|
|
708
712
|
end
|