net-ssh 7.2.3 → 8.0.4

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 (78) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.dockerignore +3 -0
  4. data/.github/pull_request_template.md +1 -0
  5. data/.github/workflows/changelog.yml +18 -0
  6. data/.github/workflows/ci-with-docker.yml +20 -9
  7. data/.github/workflows/ci.yml +10 -20
  8. data/.github/workflows/rubocop.yml +3 -1
  9. data/.rubocop.yml +5 -1
  10. data/.rubocop_todo.yml +4 -8
  11. data/CHANGES.txt +122 -0
  12. data/CONTRIBUTING.md +34 -0
  13. data/Dockerfile +4 -1
  14. data/Dockerfile.openssl3 +1 -1
  15. data/Dockerfile.zlib_ng +29 -0
  16. data/Gemfile +2 -0
  17. data/README.md +74 -57
  18. data/Rakefile +30 -12
  19. data/appveyor.yml +4 -16
  20. data/changelog.d/README.md +1 -0
  21. data/docker-compose.yml +1 -9
  22. data/lib/net/ssh/authentication/agent.rb +35 -11
  23. data/lib/net/ssh/authentication/ed25519.rb +140 -37
  24. data/lib/net/ssh/authentication/ed25519_loader.rb +14 -9
  25. data/lib/net/ssh/authentication/key_manager.rb +42 -16
  26. data/lib/net/ssh/authentication/methods/abstract.rb +2 -8
  27. data/lib/net/ssh/authentication/methods/publickey.rb +3 -1
  28. data/lib/net/ssh/authentication/security_key.rb +71 -0
  29. data/lib/net/ssh/authentication/session.rb +29 -17
  30. data/lib/net/ssh/buffer.rb +6 -1
  31. data/lib/net/ssh/buffered_io.rb +40 -3
  32. data/lib/net/ssh/config.rb +30 -13
  33. data/lib/net/ssh/connection/channel.rb +49 -2
  34. data/lib/net/ssh/connection/session.rb +151 -13
  35. data/lib/net/ssh/errors.rb +24 -6
  36. data/lib/net/ssh/key_factory.rb +11 -4
  37. data/lib/net/ssh/known_hosts.rb +62 -8
  38. data/lib/net/ssh/multiplex/client.rb +162 -0
  39. data/lib/net/ssh/multiplex/control_path.rb +120 -0
  40. data/lib/net/ssh/multiplex/proxy_transport.rb +218 -0
  41. data/lib/net/ssh/multiplex.rb +126 -0
  42. data/lib/net/ssh/prompt.rb +3 -3
  43. data/lib/net/ssh/proxy/command.rb +28 -3
  44. data/lib/net/ssh/proxy/jump.rb +71 -8
  45. data/lib/net/ssh/service/forward.rb +44 -27
  46. data/lib/net/ssh/test/extensions.rb +3 -2
  47. data/lib/net/ssh/test/socket.rb +1 -1
  48. data/lib/net/ssh/test.rb +2 -1
  49. data/lib/net/ssh/transport/aes128_gcm.rb +40 -0
  50. data/lib/net/ssh/transport/aes256_gcm.rb +40 -0
  51. data/lib/net/ssh/transport/algorithms.rb +96 -34
  52. data/lib/net/ssh/transport/chacha20_poly1305_cipher.rb +89 -16
  53. data/lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb +20 -1
  54. data/lib/net/ssh/transport/cipher_factory.rb +32 -8
  55. data/lib/net/ssh/transport/constants.rb +1 -0
  56. data/lib/net/ssh/transport/gcm_cipher.rb +228 -0
  57. data/lib/net/ssh/transport/hmac/abstract.rb +16 -0
  58. data/lib/net/ssh/transport/hmac/sha1_etm.rb +12 -0
  59. data/lib/net/ssh/transport/hmac.rb +2 -0
  60. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +41 -9
  61. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +14 -6
  62. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +8 -0
  63. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +45 -15
  64. data/lib/net/ssh/transport/openssl.rb +2 -1
  65. data/lib/net/ssh/transport/packet_stream.rb +19 -3
  66. data/lib/net/ssh/transport/server_version.rb +33 -13
  67. data/lib/net/ssh/transport/session.rb +61 -46
  68. data/lib/net/ssh/transport/state.rb +1 -1
  69. data/lib/net/ssh/verifiers/always.rb +43 -8
  70. data/lib/net/ssh/version.rb +3 -3
  71. data/lib/net/ssh.rb +148 -63
  72. data/net-ssh-public_cert.pem +7 -7
  73. data/net-ssh.gemspec +6 -8
  74. data.tar.gz.sig +0 -0
  75. metadata +64 -46
  76. metadata.gz.sig +0 -0
  77. data/Gemfile.noed25519 +0 -12
  78. data/Gemfile.norbnacl +0 -12
@@ -27,6 +27,10 @@ module Net
27
27
  # The standard port for the SSH protocol.
28
28
  DEFAULT_PORT = 22
29
29
 
30
+ # Packets held back during a re-key are bounded so a server can't exhaust memory (GHSA-ggxh-cfwq-5xqc).
31
+ MAX_QUEUED_PACKETS = 16_384
32
+ MAX_QUEUED_BYTES = 32 * 1024 * 1024
33
+
30
34
  # The host to connect to, as given to the constructor.
31
35
  attr_reader :host
32
36
 
@@ -62,6 +66,7 @@ module Net
62
66
  @port = options[:port] || DEFAULT_PORT
63
67
  @bind_address = options[:bind_address] || nil
64
68
  @options = options
69
+ @host_key_verifier = select_host_key_verifier(options[:verify_host_key])
65
70
 
66
71
  @socket =
67
72
  if (factory = options[:proxy])
@@ -75,12 +80,12 @@ module Net
75
80
 
76
81
  @socket.extend(PacketStream)
77
82
  @socket.logger = @logger
83
+ set_no_delay unless options[:no_delay] == false
78
84
 
79
85
  debug { "connection established" }
80
86
 
81
87
  @queue = []
82
-
83
- @host_key_verifier = select_host_key_verifier(options[:verify_host_key])
88
+ @queued_bytes = 0
84
89
 
85
90
  @server_version = ServerVersion.new(socket, logger, options[:timeout])
86
91
 
@@ -88,7 +93,11 @@ module Net
88
93
  @algorithms.start
89
94
  wait { algorithms.initialized? }
90
95
  rescue Errno::ETIMEDOUT
96
+ @socket&.close
91
97
  raise Net::SSH::ConnectionTimeout
98
+ rescue StandardError
99
+ @socket&.close
100
+ raise
92
101
  end
93
102
 
94
103
  def host_keys
@@ -98,12 +107,16 @@ module Net
98
107
  end
99
108
  end
100
109
 
110
+ def pubkey_algorithms
111
+ algorithms[:pubkey_algorithms]
112
+ end
113
+
101
114
  # Returns the host (and possibly IP address) in a format compatible with
102
115
  # SSH known-host files.
103
116
  def host_as_string
104
117
  @host_as_string ||= begin
105
- string = "#{host}"
106
- string = "[#{string}]:#{port}" if port != DEFAULT_PORT
118
+ string = +"#{host}"
119
+ string = +"[#{string}]:#{port}" if port != DEFAULT_PORT
107
120
 
108
121
  peer_ip = socket.peer_ip
109
122
 
@@ -188,7 +201,7 @@ module Net
188
201
  # read from the queue before the socket is queried.
189
202
  def poll_message(mode = :nonblock, consume_queue = true)
190
203
  loop do
191
- return @queue.shift if consume_queue && @queue.any? && algorithms.allow?(@queue.first)
204
+ return shift_queue if consume_queue && @queue.any? && algorithms.allow?(@queue.first)
192
205
 
193
206
  packet = socket.next_packet(mode, options[:timeout])
194
207
  return nil if packet.nil?
@@ -206,11 +219,17 @@ module Net
206
219
  when DEBUG
207
220
  send(packet[:always_display] ? :fatal : :debug) { packet[:message] }
208
221
 
222
+ # We don't advertise ext-info-c, but some servers send EXT_INFO anyway (#955).
223
+ when EXT_INFO
224
+ debug { "ignoring EXT_INFO packet" }
225
+
209
226
  when KEXINIT
210
227
  algorithms.accept_kexinit(packet)
211
228
 
212
229
  else
213
230
  return packet if algorithms.allow?(packet)
231
+ # Algorithms#rekey! clears initialized?, so the session id is what marks the first key exchange as done.
232
+ raise Net::SSH::Exception, "unexpected packet type #{packet.type} during initial key exchange" unless algorithms.session_id
214
233
 
215
234
  push(packet)
216
235
  end
@@ -234,6 +253,11 @@ module Net
234
253
  # #poll_message will return packets from the queue in the order they
235
254
  # were received.
236
255
  def push(packet)
256
+ @queued_bytes += packet.length
257
+ if @queue.size >= MAX_QUEUED_PACKETS || @queued_bytes > MAX_QUEUED_BYTES
258
+ raise Net::SSH::Exception, "too many packets queued during key exchange"
259
+ end
260
+
237
261
  @queue.push(packet)
238
262
  end
239
263
 
@@ -276,23 +300,31 @@ module Net
276
300
 
277
301
  private
278
302
 
279
- # Compatibility verifier which allows users to keep using
280
- # custom verifier code without adding new :verify_signature
281
- # method.
282
- class CompatibleVerifier
283
- def initialize(verifier)
284
- @verifier = verifier
285
- end
303
+ def shift_queue
304
+ packet = @queue.shift
305
+ @queued_bytes -= packet.length
306
+ packet
307
+ end
286
308
 
287
- def verify(arguments)
288
- @verifier.verify(arguments)
289
- end
309
+ # Turns Nagle's algorithm off on the socket, so that a packet written
310
+ # while the previous one is still unacknowledged goes out right away
311
+ # instead of waiting for the server's ACK. A ProxyCommand hands the
312
+ # transport a pipe rather than a socket, and the command on its other
313
+ # end owns the TCP connection, so there is nothing to set in that case.
314
+ def set_no_delay
315
+ return unless socket.respond_to?(:setsockopt)
290
316
 
291
- def verify_signature(&block)
292
- yield
293
- end
317
+ debug { "setting TCP_NODELAY" }
318
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
294
319
  end
295
320
 
321
+ REMOVED_VERIFY_HOST_KEY_VALUES = {
322
+ false => :never,
323
+ true => :accept_new_or_local_tunnel,
324
+ very: :accept_new,
325
+ secure: :always
326
+ }.freeze
327
+
296
328
  # Instantiates a new host-key verification class, based on the value of
297
329
  # the parameter.
298
330
  #
@@ -306,46 +338,29 @@ module Net
306
338
  #
307
339
  # If the argument happens to respond to :verify and :verify_signature,
308
340
  # it is returned directly. Otherwise, an exception is raised.
309
- #
310
- # Values false, true, and :very were deprecated in
311
- # [#595](https://github.com/net-ssh/net-ssh/pull/595)
312
341
  def select_host_key_verifier(verifier)
313
342
  case verifier
314
- when false
315
- Kernel.warn('verify_host_key: false is deprecated, use :never')
316
- Net::SSH::Verifiers::Never.new
317
343
  when :never then
318
344
  Net::SSH::Verifiers::Never.new
319
- when true
320
- Kernel.warn('verify_host_key: true is deprecated, use :accept_new_or_local_tunnel')
321
- Net::SSH::Verifiers::AcceptNewOrLocalTunnel.new
322
345
  when :accept_new_or_local_tunnel, nil then
323
346
  Net::SSH::Verifiers::AcceptNewOrLocalTunnel.new
324
- when :very
325
- Kernel.warn('verify_host_key: :very is deprecated, use :accept_new')
326
- Net::SSH::Verifiers::AcceptNew.new
327
347
  when :accept_new then
328
348
  Net::SSH::Verifiers::AcceptNew.new
329
- when :secure then
330
- Kernel.warn('verify_host_key: :secure is deprecated, use :always')
331
- Net::SSH::Verifiers::Always.new
332
349
  when :always then
333
350
  Net::SSH::Verifiers::Always.new
351
+ when *REMOVED_VERIFY_HOST_KEY_VALUES.keys
352
+ raise ArgumentError,
353
+ "verify_host_key: #{verifier.inspect} has been removed, use #{REMOVED_VERIFY_HOST_KEY_VALUES[verifier].inspect}"
334
354
  else
335
- if verifier.respond_to?(:verify)
336
- if verifier.respond_to?(:verify_signature)
337
- verifier
338
- else
339
- Kernel.warn("Warning: verifier without :verify_signature is deprecated")
340
- CompatibleVerifier.new(verifier)
341
- end
342
- else
343
- raise(
344
- ArgumentError,
345
- "Invalid argument to :verify_host_key (or deprecated " \
346
- ":paranoid): #{verifier.inspect}"
347
- )
355
+ raise ArgumentError, "Invalid argument to :verify_host_key: #{verifier.inspect}" unless verifier.respond_to?(:verify)
356
+
357
+ unless verifier.respond_to?(:verify_signature)
358
+ raise ArgumentError,
359
+ "verify_host_key verifier #{verifier.inspect} must implement verify_signature(&block): " \
360
+ "yield to the block, which checks the server's host key signature, and return its result"
348
361
  end
362
+
363
+ verifier
349
364
  end
350
365
  end
351
366
  end
@@ -125,7 +125,7 @@ module Net
125
125
  compressor.deflate(data, Zlib::SYNC_FLUSH)
126
126
  end
127
127
 
128
- # Deompresses the data. If no compression is in effect, this will just return
128
+ # Decompresses the data. If no compression is in effect, this will just return
129
129
  # the data unmodified, otherwise it uses #decompressor to decompress the data.
130
130
  def decompress(data)
131
131
  data = data.to_s
@@ -19,10 +19,8 @@ module Net
19
19
  # We've never seen this host before, so raise an exception.
20
20
  process_cache_miss(host_keys, arguments, HostKeyUnknown, "is unknown") if host_keys.empty?
21
21
 
22
- # If we found any matches, check to see that the key type and
23
- # blob also match.
24
-
25
- found = host_keys.any? do |key|
22
+ # Find the known-hosts entry whose key matches the presented certificate.
23
+ found_key = host_keys.find do |key|
26
24
  if key.respond_to?(:matches_key?)
27
25
  key.matches_key?(arguments[:key])
28
26
  else
@@ -30,11 +28,17 @@ module Net
30
28
  end
31
29
  end
32
30
 
33
- # If a match was found, return true. Otherwise, raise an exception
34
- # indicating that the key was not recognized.
35
- process_cache_miss(host_keys, arguments, HostKeyMismatch, "does not match") unless found
31
+ # No matching entry found — key is not recognized.
32
+ process_cache_miss(host_keys, arguments, HostKeyMismatch, "does not match") unless found_key
33
+
34
+ # For @cert-authority entries, enforce the certificate constraints
35
+ # OpenSSH checks in sshkey_cert_check_authority. A certificate signed by
36
+ # the trusted CA but failing any of these is rejected with
37
+ # HostKeyMismatch (not HostKeyUnknown) so AcceptNew does not swallow the
38
+ # failure and remember the rejected certificate.
39
+ verify_certificate!(found_key, host_keys, arguments)
36
40
 
37
- found
41
+ true
38
42
  end
39
43
 
40
44
  def verify_signature(&block)
@@ -43,6 +47,37 @@ module Net
43
47
 
44
48
  private
45
49
 
50
+ # Enforces the @cert-authority certificate constraints for entries that
51
+ # implement them (Net::SSH::HostKeyEntries::CertAuthority). Non-certificate
52
+ # entries do not respond to these predicates and are left untouched.
53
+ def verify_certificate!(entry, host_keys, arguments)
54
+ cert = arguments[:key]
55
+
56
+ if entry.respond_to?(:matches_validity?) && !entry.matches_validity?(cert)
57
+ reason = if cert.valid_before && cert.valid_before < Time.now
58
+ "Certificate has expired"
59
+ else
60
+ "Certificate is not yet valid"
61
+ end
62
+ process_cache_miss(host_keys, arguments, HostKeyMismatch, reason)
63
+ end
64
+
65
+ if entry.respond_to?(:matches_principal?) && !entry.matches_principal?(cert, host_keys.hostname)
66
+ process_cache_miss(host_keys, arguments, HostKeyMismatch,
67
+ "Certificate invalid: name is not a listed principal")
68
+ end
69
+
70
+ if entry.respond_to?(:matches_type?) && !entry.matches_type?(cert)
71
+ process_cache_miss(host_keys, arguments, HostKeyMismatch,
72
+ "Certificate invalid: not a host certificate")
73
+ end
74
+
75
+ return unless entry.respond_to?(:critical_options_supported?) && !entry.critical_options_supported?(cert)
76
+
77
+ process_cache_miss(host_keys, arguments, HostKeyMismatch,
78
+ "Certificate invalid: unsupported critical option")
79
+ end
80
+
46
81
  def process_cache_miss(host_keys, args, exc_class, message)
47
82
  exception = exc_class.new("fingerprint #{args[:fingerprint]} " +
48
83
  "#{message} for #{host_keys.host.inspect}")
@@ -46,13 +46,13 @@ module Net
46
46
  end
47
47
 
48
48
  # The major component of this version of the Net::SSH library
49
- MAJOR = 7
49
+ MAJOR = 8
50
50
 
51
51
  # The minor component of this version of the Net::SSH library
52
- MINOR = 2
52
+ MINOR = 0
53
53
 
54
54
  # The tiny component of this version of the Net::SSH library
55
- TINY = 3
55
+ TINY = 4
56
56
 
57
57
  # The prerelease component of this version of the Net::SSH library
58
58
  # nil allowed
data/lib/net/ssh.rb CHANGED
@@ -1,7 +1,3 @@
1
- # Make sure HOME is set, regardless of OS, so that File.expand_path works
2
- # as expected with tilde characters.
3
- ENV['HOME'] ||= ENV['HOMEPATH'] ? "#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}" : Dir.pwd
4
-
5
1
  require 'logger'
6
2
  require 'etc'
7
3
  require 'shellwords'
@@ -12,6 +8,7 @@ require 'net/ssh/loggable'
12
8
  require 'net/ssh/transport/session'
13
9
  require 'net/ssh/authentication/session'
14
10
  require 'net/ssh/connection/session'
11
+ require 'net/ssh/multiplex'
15
12
  require 'net/ssh/prompt'
16
13
 
17
14
  module Net
@@ -66,14 +63,15 @@ module Net
66
63
  auth_methods bind_address compression compression_level config
67
64
  encryption forward_agent hmac host_key identity_agent remote_user
68
65
  keepalive keepalive_interval keepalive_maxcount kex keys key_data
69
- keycerts keycert_data languages logger paranoid password port proxy
66
+ keycerts keycert_data languages logger password port proxy
70
67
  rekey_blocks_limit rekey_limit rekey_packet_limit timeout verbose
71
68
  known_hosts global_known_hosts_file user_known_hosts_file host_key_alias
72
69
  host_name user properties passphrase keys_only max_pkt_size
73
70
  max_win_size send_env set_env use_agent number_of_password_prompts
74
71
  append_all_supported_algorithms non_interactive password_prompt
75
72
  agent_socket_factory minimum_dh_bits verify_host_key
76
- fingerprint_hash check_host_ip pubkey_algorithms
73
+ fingerprint_hash check_host_ip pubkey_algorithms no_delay
74
+ control_master control_path preopen_channels
77
75
  ]
78
76
 
79
77
  # The standard means of starting a new SSH connection. When used with a
@@ -102,24 +100,62 @@ module Net
102
100
  # ssh.loop
103
101
  # end
104
102
  #
105
- # This method accepts the following options (all are optional):
103
+ # This method accepts the following options (all are optional). Omit an
104
+ # option to use its default or ssh_config value; passing +nil+ (or an array
105
+ # containing +nil+) raises ArgumentError, except for :password,
106
+ # :passphrase, :logger and :password_prompt, where +nil+ means the default.
106
107
  #
107
108
  # * :auth_methods => an array of authentication methods to try
108
109
  # * :bind_address => the IP address on the connecting machine to use in
109
110
  # establishing connection. (:bind_address is discarded if :proxy
110
111
  # is set.)
111
- # * :check_host_ip => Also ckeck IP address when connecting to remote host.
112
+ # * :check_host_ip => Also check IP address when connecting to remote host.
112
113
  # Defaults to +true+.
113
114
  # * :compression => the compression algorithm to use, or +true+ to use
114
115
  # whatever is supported.
115
116
  # * :compression_level => the compression level to use when sending data
117
+ # * :control_master => set to +:auto+ to run the session through an
118
+ # already running OpenSSH ControlMaster for the same host, port and user
119
+ # (started e.g. with <tt>ssh -M</tt> or <tt>ControlMaster auto</tt>),
120
+ # using its proxy mode (OpenSSH 7.4 or later). This skips the TCP
121
+ # connection, key exchange and authentication. If there is no usable
122
+ # master (no ControlPath, no socket, refused, timed out, owned by another
123
+ # user) Net::SSH logs why and connects as usual; it never starts a master
124
+ # itself. The master's own settings then apply instead of the options
125
+ # for setting up a connection (:keys, :key_data, :password, :passphrase,
126
+ # :auth_methods, :verify_host_key, :known_hosts, :host_key, :kex,
127
+ # :encryption, :hmac, :compression, :rekey_*, :proxy, :keepalive and the
128
+ # like); explicitly passed ones are named at info level. Over the
129
+ # master, remote port forwarding (Service::Forward#remote and
130
+ # #cancel_remote) raises Net::SSH::NotSupportedOverControlMaster, as the
131
+ # master consumes the global request reply, so success, failure and the
132
+ # port allocated for port 0 can't be observed; other global requests
133
+ # raise it too. Agent and X11 forwarding requests raise it
134
+ # too, and :forward_agent is ignored (logged at info level), because the
135
+ # master serves them from its own agent or display only if it has
136
+ # ForwardAgent or ForwardX11 enabled itself, and rejects them otherwise.
137
+ # Keepalive and rekeying are left to the master. ssh_config's
138
+ # ControlMaster is ignored, only this option enables it. An invalid
139
+ # ControlPath raises ArgumentError. A missing or stale socket is logged
140
+ # at info level, a socket that can't be used (another owner, refused,
141
+ # wrong protocol, timed out) at warn level. Has no effect on Windows.
142
+ # Defaults to +false+.
143
+ # * :control_path => the ControlMaster socket to use with
144
+ # :control_master, with the same ~, ${ENV} and % tokens as ssh_config's
145
+ # ControlPath (%C, %d, %h, %i, %j, %k, %L, %l, %n, %p, %r, %u, %%).
146
+ # The master at that path decides which host and user the session is
147
+ # connected to, so a fixed path without %h, %p, %r or %C sends every
148
+ # host through the same master and the explicitly passed host key and
149
+ # authentication options are ignored. Defaults to ControlPath from
150
+ # ssh_config; +"none"+ disables the master.
116
151
  # * :config => set to +true+ to load the default OpenSSH config files
117
152
  # (~/.ssh/config, /etc/ssh_config), or to +false+ to not load them, or to
118
153
  # a file-name (or array of file-names) to load those specific configuration
119
154
  # files. Defaults to +true+.
120
155
  # * :encryption => the encryption cipher (or ciphers) to use
121
156
  # * :forward_agent => set to true if you want the SSH agent connection to
122
- # be forwarded
157
+ # be forwarded. Ignored for a session through a ControlMaster (see
158
+ # :control_master).
123
159
  # * :known_hosts => a custom object holding known hosts records.
124
160
  # It must implement #search_for and `add` in a similiar manner as KnownHosts.
125
161
  # * :global_known_hosts_file => the location of the global known hosts
@@ -141,7 +177,11 @@ module Net
141
177
  # Defaults to +300+ seconds.
142
178
  # * :keepalive_maxcount => the maximun number of keepalive packet miss allowed.
143
179
  # Defaults to 3
144
- # * :kex => the key exchange algorithm (or algorithms) to use
180
+ # * :kex => the key exchange algorithm (or algorithms) to use. Like
181
+ # KexAlgorithms, entries starting with +, - or ^ modify the default list.
182
+ # diffie-hellman-group14-sha1 is supported but not offered unless listed
183
+ # here (e.g. '+diffie-hellman-group14-sha1') or
184
+ # :append_all_supported_algorithms is set.
145
185
  # * :keys => an array of file names of private keys to use for publickey
146
186
  # and hostbased authentication
147
187
  # * :keycerts => an array of file names of key certificates to use
@@ -159,24 +199,44 @@ module Net
159
199
  # packet. Default is 0x8000 (32768 bytes). Increase to 0x10000 (65536 bytes)
160
200
  # for better performance if your SSH server supports it (most do).
161
201
  # * :max_win_size => maximum size we tell the other side that is supported for
162
- # the window.
202
+ # the window, i.e. how much the server may send on a channel before net-ssh
203
+ # reads it. Default is 0x200000 (2 MB), as in OpenSSH. Smaller values limit
204
+ # how much data is in flight; some servers (e.g. Mikrotik) truncate output
205
+ # when the window is small.
206
+ # * :minimum_dh_bits => the smallest group, in bits, accepted from the server
207
+ # in diffie-hellman-group-exchange key exchange. Defaults to 2048, as in
208
+ # OpenSSH; lower it (e.g. to 1024) only to reach legacy servers that offer
209
+ # nothing larger.
210
+ # * :no_delay => turns Nagle's algorithm off on the connection's socket
211
+ # (TCP_NODELAY), so a packet written while the previous one is still
212
+ # unacknowledged is sent right away instead of waiting for the server's
213
+ # ACK. OpenSSH does the same for every connection. Set to +false+ to keep
214
+ # Nagle's algorithm on. Has no effect when the socket comes from a
215
+ # ProxyCommand, whose command owns the TCP connection. Defaults to +true+.
163
216
  # * :non_interactive => set to true if your app is non interactive and prefers
164
217
  # authentication failure vs password prompt. Non-interactive applications
165
218
  # should set it to true to prefer failing a password/etc auth methods vs.
166
219
  # asking for password.
167
- # * :paranoid => deprecated alias for :verify_host_key
168
220
  # * :passphrase => the passphrase to use when loading a private key (default
169
221
  # is +nil+, for no passphrase)
170
222
  # * :password => the password to use to login
171
223
  # * :port => the port to use when connecting to the remote host
224
+ # * :preopen_channels => set to +true+ to keep one spare "session" channel
225
+ # open, so that Net::SSH::Connection::Session#open_channel can hand it
226
+ # out at once and a command that waits for the previous one to finish
227
+ # costs one round trip instead of two. The spare counts against the
228
+ # server's MaxSessions; if the server refuses it, pre-opening stops for
229
+ # the session. Defaults to +false+.
172
230
  # * :properties => a hash of key/value pairs to add to the new connection's
173
231
  # properties (see Net::SSH::Connection::Session#properties)
174
232
  # * :proxy => a proxy instance (see Proxy) to use when connecting
175
- # * :pubkey_algorithms => the public key authentication algorithms to use for
176
- # this connection. Valid values are 'rsa-sha2-256-cert-v01@openssh.com',
177
- # 'ssh-rsa-cert-v01@openssh.com', 'rsa-sha2-256', 'ssh-rsa'. Currently, this
178
- # option is only used for RSA public key authentication and ignored for other
179
- # types.
233
+ # * :pubkey_algorithms => the public key authentication algorithms to offer,
234
+ # in order of preference (e.g. %w[ssh-ed25519 rsa-sha2-512 rsa-sha2-256]).
235
+ # Keys whose type isn't listed are skipped. Like PubkeyAcceptedAlgorithms,
236
+ # entries starting with +, - or ^ modify the default list. ssh-rsa (SHA-1),
237
+ # ssh-rsa-cert-v01@openssh.com and ssh-dss are supported but not offered
238
+ # unless listed here (e.g. '+ssh-rsa') or :append_all_supported_algorithms
239
+ # is set.
180
240
  # * :rekey_blocks_limit => the max number of blocks to process before rekeying
181
241
  # * :rekey_limit => the max number of bytes to process before rekeying
182
242
  # * :rekey_packet_limit => the max number of packets to process before rekeying
@@ -188,7 +248,8 @@ module Net
188
248
  # * :user => the user name to log in as; this overrides the +user+
189
249
  # parameter, and is primarily only useful when provided via an SSH
190
250
  # configuration file.
191
- # * :remote_user => used for substitution into the '%r' part of a ProxyCommand
251
+ # * :remote_user => used for substitution into the '%r' part of a ProxyCommand;
252
+ # defaults to the user name the connection logs in as.
192
253
  # * :user_known_hosts_file => the location of the user known hosts file.
193
254
  # Set to an array to specify multiple user known hosts files.
194
255
  # Defaults to %w(~/.ssh/known_hosts ~/.ssh/known_hosts2).
@@ -200,7 +261,9 @@ module Net
200
261
  # default. The symbols :debug, :info, :warn, :error, and :fatal are also
201
262
  # supported and are translated to the corresponding Logger constant.
202
263
  # * :append_all_supported_algorithms => set to +true+ to append all supported
203
- # algorithms by net-ssh. Was the default behaviour until 2.10
264
+ # algorithms by net-ssh, except the +none+ cipher and MAC, which must be
265
+ # named explicitly (e.g. encryption: '+none'). Was the default behaviour
266
+ # until 2.10
204
267
  # * :number_of_password_prompts => Number of prompts for the password
205
268
  # authentication method defaults to 3 set to 0 to disable prompt for
206
269
  # password auth method
@@ -215,21 +278,32 @@ module Net
215
278
  # * :accept_new_or_local_tunnel (insecure) ::Net::SSH::Verifiers::AcceptNewOrLocalTunnel
216
279
  # * :accept_new (insecure) ::Net::SSH::Verifiers::AcceptNew
217
280
  # * :always (secure) ::Net::SSH::Verifiers::Always
218
- # You can also provide an own Object which responds to +verify+. The argument
219
- # given to +verify+ is a hash consisting of the +:key+, the +:key_blob+,
220
- # the +:fingerprint+ and the +:session+. Returning true accepts the host key,
221
- # returning false declines it and closes the connection.
281
+ # You can also provide an own Object which responds to +verify+ and
282
+ # +verify_signature+. The argument given to +verify+ is a hash consisting
283
+ # of the +:key+, the +:key_blob+, the +:fingerprint+ and the +:session+.
284
+ # Returning true accepts the host key, returning false declines it and
285
+ # closes the connection. +verify_signature+ is given a block that checks
286
+ # the server's host key signature and must return the block's result.
222
287
  # * :fingerprint_hash => 'MD5' or 'SHA256', defaults to 'SHA256'
223
288
  # If +user+ parameter is nil it defaults to USER from ssh_config, or
224
289
  # local username
225
290
  def self.start(host, user = nil, options = {}, &block)
291
+ if options.key?(:paranoid)
292
+ raise ArgumentError, ":paranoid has been removed, use :verify_host_key (same values)"
293
+ end
294
+
226
295
  invalid_options = options.keys - VALID_OPTIONS
227
296
  if invalid_options.any?
228
297
  raise ArgumentError, "invalid option(s): #{invalid_options.join(', ')}"
229
298
  end
230
299
 
300
+ reject_nil_options(options)
301
+ Multiplex.validate_options!(options)
302
+ explicit_options = options.keys
303
+ host_arg = host
304
+
305
+ options = options.dup
231
306
  assign_defaults(options)
232
- _sanitize_options(options)
233
307
 
234
308
  options[:user] = user if user
235
309
  options = configuration_for(host, options.fetch(:config, true)).merge(options)
@@ -241,8 +315,6 @@ module Net
241
315
  options[:number_of_password_prompts] = 0
242
316
  end
243
317
 
244
- _support_deprecated_option_paranoid(options)
245
-
246
318
  if options[:verbose]
247
319
  options[:logger].level = case options[:verbose]
248
320
  when Integer then options[:verbose]
@@ -255,26 +327,35 @@ module Net
255
327
  end
256
328
  end
257
329
 
258
- transport = Transport::Session.new(host, options)
259
- auth = Authentication::Session.new(transport, options)
260
-
261
330
  user = options.fetch(:user, user) || Etc.getpwuid.name
262
- if auth.authenticate("ssh-connection", user, options[:password])
263
- connection = Connection::Session.new(transport, options)
264
- if block_given?
265
- begin
266
- yield connection
267
- ensure
268
- connection.close unless connection.closed?
269
- end
270
- else
271
- return connection
331
+ options[:remote_user] ||= user
332
+
333
+ connection = Multiplex.connect(host_arg, host, user, options, explicit: explicit_options) if options[:control_master]
334
+ connection ||= connect_directly(host, user, options)
335
+
336
+ if block_given?
337
+ begin
338
+ yield connection
339
+ ensure
340
+ connection.close unless connection.closed?
272
341
  end
273
342
  else
343
+ connection
344
+ end
345
+ end
346
+
347
+ def self.connect_directly(host, user, options)
348
+ transport = Transport::Session.new(host, options)
349
+ auth = Authentication::Session.new(transport, options)
350
+
351
+ unless auth.authenticate("ssh-connection", user, options[:password])
274
352
  transport.close
275
353
  raise AuthenticationFailed, "Authentication failed for user #{user}@#{host}"
276
354
  end
355
+
356
+ Connection::Session.new(transport, options)
277
357
  end
358
+ private_class_method :connect_directly
278
359
 
279
360
  # Returns a hash of the configuration options for the given host, as read
280
361
  # from the SSH configuration file(s). If +use_ssh_config+ is true (the
@@ -308,31 +389,35 @@ module Net
308
389
  end
309
390
  end
310
391
 
311
- def self._sanitize_options(options)
312
- invalid_option_values = [nil, [nil]]
313
- unless (options.values & invalid_option_values).empty?
314
- nil_options = options.select { |_k, v| invalid_option_values.include?(v) }.map(&:first)
315
- Kernel.warn "#{caller_locations(2, 1)[0]}: Passing nil, or [nil] to Net::SSH.start is deprecated for keys: #{nil_options.join(', ')}"
316
- end
317
- end
318
- private_class_method :_sanitize_options
392
+ NIL_ALLOWED_OPTIONS = %i[password passphrase logger password_prompt].freeze
319
393
 
320
- def self._support_deprecated_option_paranoid(options)
321
- if options.key?(:paranoid)
322
- Kernel.warn(
323
- ":paranoid is deprecated, please use :verify_host_key. Supported " \
324
- "values are exactly the same, only the name of the option has changed."
325
- )
326
- if options.key?(:verify_host_key)
327
- Kernel.warn(
328
- "Both :paranoid and :verify_host_key were specified. " \
329
- ":verify_host_key takes precedence, :paranoid will be ignored."
330
- )
331
- else
332
- options[:verify_host_key] = options.delete(:paranoid)
333
- end
334
- end
394
+ NIL_OPTION_HINTS = {
395
+ config: "config: false to skip ssh_config files",
396
+ proxy: "proxy: false to connect without a proxy",
397
+ keys: "keys: [] to use no key files",
398
+ use_agent: "use_agent: false to not use ssh-agent",
399
+ forward_agent: "forward_agent: false to not forward ssh-agent",
400
+ keepalive: "keepalive: false to send no keepalives",
401
+ compression: "compression: false to disable compression",
402
+ control_master: "control_master: false to not use a ControlMaster",
403
+ control_path: "control_path: 'none' to not use a ControlMaster socket",
404
+ send_env: "send_env: [] to send no environment variables",
405
+ set_env: "set_env: {} to set no environment variables"
406
+ }.freeze
407
+ private_constant :NIL_ALLOWED_OPTIONS, :NIL_OPTION_HINTS
408
+
409
+ def self.reject_nil_options(options)
410
+ nil_keys = options.select do |key, value|
411
+ !NIL_ALLOWED_OPTIONS.include?(key) && (value.nil? || (value.is_a?(Array) && value.include?(nil)))
412
+ end.keys
413
+ return if nil_keys.empty?
414
+
415
+ message = "nil is not allowed for #{nil_keys.map(&:inspect).join(', ')}; " \
416
+ "omit #{nil_keys.one? ? 'it' : 'them'} to use the default or ssh_config value"
417
+ hints = NIL_OPTION_HINTS.values_at(*nil_keys).compact
418
+ message += ", or pass #{hints.join(', or ')}" if hints.any?
419
+ raise ArgumentError, message
335
420
  end
336
- private_class_method :_support_deprecated_option_paranoid
421
+ private_class_method :reject_nil_options
337
422
  end
338
423
  end