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