net-ssh 7.3.6 → 8.0.2

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 (68) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/pull_request_template.md +1 -0
  4. data/.github/workflows/changelog.yml +43 -0
  5. data/.github/workflows/ci-with-docker.yml +0 -3
  6. data/.github/workflows/ci.yml +2 -17
  7. data/.github/workflows/rubocop.yml +3 -1
  8. data/.rubocop.yml +5 -1
  9. data/.rubocop_todo.yml +1 -7
  10. data/CHANGES.txt +82 -7
  11. data/CONTRIBUTING.md +35 -0
  12. data/Dockerfile +3 -3
  13. data/Dockerfile.openssl3 +1 -1
  14. data/Dockerfile.zlib_ng +1 -1
  15. data/README.md +71 -57
  16. data/Rakefile +66 -12
  17. data/appveyor.yml +4 -16
  18. data/changelog.d/README.md +1 -0
  19. data/docker-compose.yml +1 -7
  20. data/lib/net/ssh/authentication/agent.rb +35 -11
  21. data/lib/net/ssh/authentication/ed25519.rb +137 -44
  22. data/lib/net/ssh/authentication/ed25519_loader.rb +14 -9
  23. data/lib/net/ssh/authentication/key_manager.rb +22 -11
  24. data/lib/net/ssh/authentication/methods/abstract.rb +2 -8
  25. data/lib/net/ssh/authentication/methods/publickey.rb +3 -1
  26. data/lib/net/ssh/authentication/security_key.rb +71 -0
  27. data/lib/net/ssh/authentication/session.rb +29 -17
  28. data/lib/net/ssh/buffer.rb +6 -1
  29. data/lib/net/ssh/buffered_io.rb +40 -3
  30. data/lib/net/ssh/config.rb +28 -12
  31. data/lib/net/ssh/connection/channel.rb +49 -2
  32. data/lib/net/ssh/connection/session.rb +58 -8
  33. data/lib/net/ssh/errors.rb +24 -6
  34. data/lib/net/ssh/key_factory.rb +7 -1
  35. data/lib/net/ssh/known_hosts.rb +18 -8
  36. data/lib/net/ssh/multiplex/client.rb +162 -0
  37. data/lib/net/ssh/multiplex/control_path.rb +120 -0
  38. data/lib/net/ssh/multiplex/proxy_transport.rb +218 -0
  39. data/lib/net/ssh/multiplex.rb +126 -0
  40. data/lib/net/ssh/prompt.rb +3 -3
  41. data/lib/net/ssh/proxy/jump.rb +40 -2
  42. data/lib/net/ssh/service/forward.rb +35 -24
  43. data/lib/net/ssh/test.rb +2 -1
  44. data/lib/net/ssh/transport/algorithms.rb +88 -33
  45. data/lib/net/ssh/transport/chacha20_poly1305_cipher.rb +89 -16
  46. data/lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb +20 -1
  47. data/lib/net/ssh/transport/cipher_factory.rb +23 -1
  48. data/lib/net/ssh/transport/constants.rb +1 -0
  49. data/lib/net/ssh/transport/gcm_cipher.rb +27 -6
  50. data/lib/net/ssh/transport/hmac/sha1_etm.rb +12 -0
  51. data/lib/net/ssh/transport/hmac.rb +2 -0
  52. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +41 -9
  53. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +14 -6
  54. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +8 -0
  55. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +45 -15
  56. data/lib/net/ssh/transport/openssl.rb +2 -1
  57. data/lib/net/ssh/transport/packet_stream.rb +12 -0
  58. data/lib/net/ssh/transport/session.rb +43 -45
  59. data/lib/net/ssh/verifiers/always.rb +0 -3
  60. data/lib/net/ssh/version.rb +3 -3
  61. data/lib/net/ssh.rb +141 -58
  62. data/net-ssh.gemspec +5 -8
  63. data/rakelib/changelog.rb +229 -0
  64. data.tar.gz.sig +0 -0
  65. metadata +37 -36
  66. metadata.gz.sig +0 -0
  67. data/Gemfile.noed25519 +0 -12
  68. data/Gemfile.norbnacl +0 -12
@@ -66,6 +66,7 @@ module Net
66
66
  @port = options[:port] || DEFAULT_PORT
67
67
  @bind_address = options[:bind_address] || nil
68
68
  @options = options
69
+ @host_key_verifier = select_host_key_verifier(options[:verify_host_key])
69
70
 
70
71
  @socket =
71
72
  if (factory = options[:proxy])
@@ -79,21 +80,24 @@ module Net
79
80
 
80
81
  @socket.extend(PacketStream)
81
82
  @socket.logger = @logger
83
+ set_no_delay unless options[:no_delay] == false
82
84
 
83
85
  debug { "connection established" }
84
86
 
85
87
  @queue = []
86
88
  @queued_bytes = 0
87
89
 
88
- @host_key_verifier = select_host_key_verifier(options[:verify_host_key])
89
-
90
90
  @server_version = ServerVersion.new(socket, logger, options[:timeout])
91
91
 
92
92
  @algorithms = Algorithms.new(self, options)
93
93
  @algorithms.start
94
94
  wait { algorithms.initialized? }
95
95
  rescue Errno::ETIMEDOUT
96
+ @socket&.close
96
97
  raise Net::SSH::ConnectionTimeout
98
+ rescue StandardError
99
+ @socket&.close
100
+ raise
97
101
  end
98
102
 
99
103
  def host_keys
@@ -103,6 +107,10 @@ module Net
103
107
  end
104
108
  end
105
109
 
110
+ def pubkey_algorithms
111
+ algorithms[:pubkey_algorithms]
112
+ end
113
+
106
114
  # Returns the host (and possibly IP address) in a format compatible with
107
115
  # SSH known-host files.
108
116
  def host_as_string
@@ -211,12 +219,17 @@ module Net
211
219
  when DEBUG
212
220
  send(packet[:always_display] ? :fatal : :debug) { packet[:message] }
213
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
+
214
226
  when KEXINIT
215
227
  algorithms.accept_kexinit(packet)
216
228
 
217
229
  else
218
230
  return packet if algorithms.allow?(packet)
219
- raise Net::SSH::Exception, "unexpected packet type #{packet.type} during initial key exchange" unless algorithms.initialized?
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
220
233
 
221
234
  push(packet)
222
235
  end
@@ -293,23 +306,25 @@ module Net
293
306
  packet
294
307
  end
295
308
 
296
- # Compatibility verifier which allows users to keep using
297
- # custom verifier code without adding new :verify_signature
298
- # method.
299
- class CompatibleVerifier
300
- def initialize(verifier)
301
- @verifier = verifier
302
- 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)
303
316
 
304
- def verify(arguments)
305
- @verifier.verify(arguments)
306
- end
307
-
308
- def verify_signature(&block)
309
- yield
310
- end
317
+ debug { "setting TCP_NODELAY" }
318
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
311
319
  end
312
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
+
313
328
  # Instantiates a new host-key verification class, based on the value of
314
329
  # the parameter.
315
330
  #
@@ -323,46 +338,29 @@ module Net
323
338
  #
324
339
  # If the argument happens to respond to :verify and :verify_signature,
325
340
  # it is returned directly. Otherwise, an exception is raised.
326
- #
327
- # Values false, true, and :very were deprecated in
328
- # [#595](https://github.com/net-ssh/net-ssh/pull/595)
329
341
  def select_host_key_verifier(verifier)
330
342
  case verifier
331
- when false
332
- Kernel.warn('verify_host_key: false is deprecated, use :never')
333
- Net::SSH::Verifiers::Never.new
334
343
  when :never then
335
344
  Net::SSH::Verifiers::Never.new
336
- when true
337
- Kernel.warn('verify_host_key: true is deprecated, use :accept_new_or_local_tunnel')
338
- Net::SSH::Verifiers::AcceptNewOrLocalTunnel.new
339
345
  when :accept_new_or_local_tunnel, nil then
340
346
  Net::SSH::Verifiers::AcceptNewOrLocalTunnel.new
341
- when :very
342
- Kernel.warn('verify_host_key: :very is deprecated, use :accept_new')
343
- Net::SSH::Verifiers::AcceptNew.new
344
347
  when :accept_new then
345
348
  Net::SSH::Verifiers::AcceptNew.new
346
- when :secure then
347
- Kernel.warn('verify_host_key: :secure is deprecated, use :always')
348
- Net::SSH::Verifiers::Always.new
349
349
  when :always then
350
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}"
351
354
  else
352
- if verifier.respond_to?(:verify)
353
- if verifier.respond_to?(:verify_signature)
354
- verifier
355
- else
356
- Kernel.warn("Warning: verifier without :verify_signature is deprecated")
357
- CompatibleVerifier.new(verifier)
358
- end
359
- else
360
- raise(
361
- ArgumentError,
362
- "Invalid argument to :verify_host_key (or deprecated " \
363
- ":paranoid): #{verifier.inspect}"
364
- )
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"
365
361
  end
362
+
363
+ verifier
366
364
  end
367
365
  end
368
366
  end
@@ -51,9 +51,6 @@ module Net
51
51
  # implement them (Net::SSH::HostKeyEntries::CertAuthority). Non-certificate
52
52
  # entries do not respond to these predicates and are left untouched.
53
53
  def verify_certificate!(entry, host_keys, arguments)
54
- # Plain keys carry no certificate constraints, and respond_to? on this delegator prints a deprecation warning (#1026).
55
- return if entry.is_a?(Net::SSH::HostKeyEntries::PubKey)
56
-
57
54
  cert = arguments[:key]
58
55
 
59
56
  if entry.respond_to?(:matches_validity?) && !entry.matches_validity?(cert)
@@ -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 = 3
52
+ MINOR = 0
53
53
 
54
54
  # The tiny component of this version of the Net::SSH library
55
- TINY = 6
55
+ TINY = 2
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
@@ -8,6 +8,7 @@ require 'net/ssh/loggable'
8
8
  require 'net/ssh/transport/session'
9
9
  require 'net/ssh/authentication/session'
10
10
  require 'net/ssh/connection/session'
11
+ require 'net/ssh/multiplex'
11
12
  require 'net/ssh/prompt'
12
13
 
13
14
  module Net
@@ -62,14 +63,15 @@ module Net
62
63
  auth_methods bind_address compression compression_level config
63
64
  encryption forward_agent hmac host_key identity_agent remote_user
64
65
  keepalive keepalive_interval keepalive_maxcount kex keys key_data
65
- keycerts keycert_data languages logger paranoid password port proxy
66
+ keycerts keycert_data languages logger password port proxy
66
67
  rekey_blocks_limit rekey_limit rekey_packet_limit timeout verbose
67
68
  known_hosts global_known_hosts_file user_known_hosts_file host_key_alias
68
69
  host_name user properties passphrase keys_only max_pkt_size
69
70
  max_win_size send_env set_env use_agent number_of_password_prompts
70
71
  append_all_supported_algorithms non_interactive password_prompt
71
72
  agent_socket_factory minimum_dh_bits verify_host_key
72
- fingerprint_hash check_host_ip pubkey_algorithms
73
+ fingerprint_hash check_host_ip pubkey_algorithms no_delay
74
+ control_master control_path
73
75
  ]
74
76
 
75
77
  # The standard means of starting a new SSH connection. When used with a
@@ -98,7 +100,10 @@ module Net
98
100
  # ssh.loop
99
101
  # end
100
102
  #
101
- # 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.
102
107
  #
103
108
  # * :auth_methods => an array of authentication methods to try
104
109
  # * :bind_address => the IP address on the connecting machine to use in
@@ -109,13 +114,48 @@ module Net
109
114
  # * :compression => the compression algorithm to use, or +true+ to use
110
115
  # whatever is supported.
111
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.
112
151
  # * :config => set to +true+ to load the default OpenSSH config files
113
152
  # (~/.ssh/config, /etc/ssh_config), or to +false+ to not load them, or to
114
153
  # a file-name (or array of file-names) to load those specific configuration
115
154
  # files. Defaults to +true+.
116
155
  # * :encryption => the encryption cipher (or ciphers) to use
117
156
  # * :forward_agent => set to true if you want the SSH agent connection to
118
- # be forwarded
157
+ # be forwarded. Ignored for a session through a ControlMaster (see
158
+ # :control_master).
119
159
  # * :known_hosts => a custom object holding known hosts records.
120
160
  # It must implement #search_for and `add` in a similiar manner as KnownHosts.
121
161
  # * :global_known_hosts_file => the location of the global known hosts
@@ -137,7 +177,11 @@ module Net
137
177
  # Defaults to +300+ seconds.
138
178
  # * :keepalive_maxcount => the maximun number of keepalive packet miss allowed.
139
179
  # Defaults to 3
140
- # * :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.
141
185
  # * :keys => an array of file names of private keys to use for publickey
142
186
  # and hostbased authentication
143
187
  # * :keycerts => an array of file names of key certificates to use
@@ -155,12 +199,24 @@ module Net
155
199
  # packet. Default is 0x8000 (32768 bytes). Increase to 0x10000 (65536 bytes)
156
200
  # for better performance if your SSH server supports it (most do).
157
201
  # * :max_win_size => maximum size we tell the other side that is supported for
158
- # 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+.
159
216
  # * :non_interactive => set to true if your app is non interactive and prefers
160
217
  # authentication failure vs password prompt. Non-interactive applications
161
218
  # should set it to true to prefer failing a password/etc auth methods vs.
162
219
  # asking for password.
163
- # * :paranoid => deprecated alias for :verify_host_key
164
220
  # * :passphrase => the passphrase to use when loading a private key (default
165
221
  # is +nil+, for no passphrase)
166
222
  # * :password => the password to use to login
@@ -168,11 +224,13 @@ module Net
168
224
  # * :properties => a hash of key/value pairs to add to the new connection's
169
225
  # properties (see Net::SSH::Connection::Session#properties)
170
226
  # * :proxy => a proxy instance (see Proxy) to use when connecting
171
- # * :pubkey_algorithms => the public key authentication algorithms to use for
172
- # this connection. Valid values are 'rsa-sha2-256-cert-v01@openssh.com',
173
- # 'ssh-rsa-cert-v01@openssh.com', 'rsa-sha2-256', 'ssh-rsa'. Currently, this
174
- # option is only used for RSA public key authentication and ignored for other
175
- # types.
227
+ # * :pubkey_algorithms => the public key authentication algorithms to offer,
228
+ # in order of preference (e.g. %w[ssh-ed25519 rsa-sha2-512 rsa-sha2-256]).
229
+ # Keys whose type isn't listed are skipped. Like PubkeyAcceptedAlgorithms,
230
+ # entries starting with +, - or ^ modify the default list. ssh-rsa (SHA-1),
231
+ # ssh-rsa-cert-v01@openssh.com and ssh-dss are supported but not offered
232
+ # unless listed here (e.g. '+ssh-rsa') or :append_all_supported_algorithms
233
+ # is set.
176
234
  # * :rekey_blocks_limit => the max number of blocks to process before rekeying
177
235
  # * :rekey_limit => the max number of bytes to process before rekeying
178
236
  # * :rekey_packet_limit => the max number of packets to process before rekeying
@@ -184,7 +242,8 @@ module Net
184
242
  # * :user => the user name to log in as; this overrides the +user+
185
243
  # parameter, and is primarily only useful when provided via an SSH
186
244
  # configuration file.
187
- # * :remote_user => used for substitution into the '%r' part of a ProxyCommand
245
+ # * :remote_user => used for substitution into the '%r' part of a ProxyCommand;
246
+ # defaults to the user name the connection logs in as.
188
247
  # * :user_known_hosts_file => the location of the user known hosts file.
189
248
  # Set to an array to specify multiple user known hosts files.
190
249
  # Defaults to %w(~/.ssh/known_hosts ~/.ssh/known_hosts2).
@@ -196,7 +255,9 @@ module Net
196
255
  # default. The symbols :debug, :info, :warn, :error, and :fatal are also
197
256
  # supported and are translated to the corresponding Logger constant.
198
257
  # * :append_all_supported_algorithms => set to +true+ to append all supported
199
- # algorithms by net-ssh. Was the default behaviour until 2.10
258
+ # algorithms by net-ssh, except the +none+ cipher and MAC, which must be
259
+ # named explicitly (e.g. encryption: '+none'). Was the default behaviour
260
+ # until 2.10
200
261
  # * :number_of_password_prompts => Number of prompts for the password
201
262
  # authentication method defaults to 3 set to 0 to disable prompt for
202
263
  # password auth method
@@ -211,21 +272,32 @@ module Net
211
272
  # * :accept_new_or_local_tunnel (insecure) ::Net::SSH::Verifiers::AcceptNewOrLocalTunnel
212
273
  # * :accept_new (insecure) ::Net::SSH::Verifiers::AcceptNew
213
274
  # * :always (secure) ::Net::SSH::Verifiers::Always
214
- # You can also provide an own Object which responds to +verify+. The argument
215
- # given to +verify+ is a hash consisting of the +:key+, the +:key_blob+,
216
- # the +:fingerprint+ and the +:session+. Returning true accepts the host key,
217
- # returning false declines it and closes the connection.
275
+ # You can also provide an own Object which responds to +verify+ and
276
+ # +verify_signature+. The argument given to +verify+ is a hash consisting
277
+ # of the +:key+, the +:key_blob+, the +:fingerprint+ and the +:session+.
278
+ # Returning true accepts the host key, returning false declines it and
279
+ # closes the connection. +verify_signature+ is given a block that checks
280
+ # the server's host key signature and must return the block's result.
218
281
  # * :fingerprint_hash => 'MD5' or 'SHA256', defaults to 'SHA256'
219
282
  # If +user+ parameter is nil it defaults to USER from ssh_config, or
220
283
  # local username
221
284
  def self.start(host, user = nil, options = {}, &block)
285
+ if options.key?(:paranoid)
286
+ raise ArgumentError, ":paranoid has been removed, use :verify_host_key (same values)"
287
+ end
288
+
222
289
  invalid_options = options.keys - VALID_OPTIONS
223
290
  if invalid_options.any?
224
291
  raise ArgumentError, "invalid option(s): #{invalid_options.join(', ')}"
225
292
  end
226
293
 
294
+ reject_nil_options(options)
295
+ Multiplex.validate_options!(options)
296
+ explicit_options = options.keys
297
+ host_arg = host
298
+
299
+ options = options.dup
227
300
  assign_defaults(options)
228
- _sanitize_options(options)
229
301
 
230
302
  options[:user] = user if user
231
303
  options = configuration_for(host, options.fetch(:config, true)).merge(options)
@@ -237,8 +309,6 @@ module Net
237
309
  options[:number_of_password_prompts] = 0
238
310
  end
239
311
 
240
- _support_deprecated_option_paranoid(options)
241
-
242
312
  if options[:verbose]
243
313
  options[:logger].level = case options[:verbose]
244
314
  when Integer then options[:verbose]
@@ -251,26 +321,35 @@ module Net
251
321
  end
252
322
  end
253
323
 
254
- transport = Transport::Session.new(host, options)
255
- auth = Authentication::Session.new(transport, options)
256
-
257
324
  user = options.fetch(:user, user) || Etc.getpwuid.name
258
- if auth.authenticate("ssh-connection", user, options[:password])
259
- connection = Connection::Session.new(transport, options)
260
- if block_given?
261
- begin
262
- yield connection
263
- ensure
264
- connection.close unless connection.closed?
265
- end
266
- else
267
- return connection
325
+ options[:remote_user] ||= user
326
+
327
+ connection = Multiplex.connect(host_arg, host, user, options, explicit: explicit_options) if options[:control_master]
328
+ connection ||= connect_directly(host, user, options)
329
+
330
+ if block_given?
331
+ begin
332
+ yield connection
333
+ ensure
334
+ connection.close unless connection.closed?
268
335
  end
269
336
  else
337
+ connection
338
+ end
339
+ end
340
+
341
+ def self.connect_directly(host, user, options)
342
+ transport = Transport::Session.new(host, options)
343
+ auth = Authentication::Session.new(transport, options)
344
+
345
+ unless auth.authenticate("ssh-connection", user, options[:password])
270
346
  transport.close
271
347
  raise AuthenticationFailed, "Authentication failed for user #{user}@#{host}"
272
348
  end
349
+
350
+ Connection::Session.new(transport, options)
273
351
  end
352
+ private_class_method :connect_directly
274
353
 
275
354
  # Returns a hash of the configuration options for the given host, as read
276
355
  # from the SSH configuration file(s). If +use_ssh_config+ is true (the
@@ -304,31 +383,35 @@ module Net
304
383
  end
305
384
  end
306
385
 
307
- def self._sanitize_options(options)
308
- invalid_option_values = [nil, [nil]]
309
- unless (options.values & invalid_option_values).empty?
310
- nil_options = options.select { |_k, v| invalid_option_values.include?(v) }.map(&:first)
311
- Kernel.warn "#{caller_locations(2, 1)[0]}: Passing nil, or [nil] to Net::SSH.start is deprecated for keys: #{nil_options.join(', ')}"
312
- end
313
- end
314
- private_class_method :_sanitize_options
386
+ NIL_ALLOWED_OPTIONS = %i[password passphrase logger password_prompt].freeze
315
387
 
316
- def self._support_deprecated_option_paranoid(options)
317
- if options.key?(:paranoid)
318
- Kernel.warn(
319
- ":paranoid is deprecated, please use :verify_host_key. Supported " \
320
- "values are exactly the same, only the name of the option has changed."
321
- )
322
- if options.key?(:verify_host_key)
323
- Kernel.warn(
324
- "Both :paranoid and :verify_host_key were specified. " \
325
- ":verify_host_key takes precedence, :paranoid will be ignored."
326
- )
327
- else
328
- options[:verify_host_key] = options.delete(:paranoid)
329
- end
330
- end
388
+ NIL_OPTION_HINTS = {
389
+ config: "config: false to skip ssh_config files",
390
+ proxy: "proxy: false to connect without a proxy",
391
+ keys: "keys: [] to use no key files",
392
+ use_agent: "use_agent: false to not use ssh-agent",
393
+ forward_agent: "forward_agent: false to not forward ssh-agent",
394
+ keepalive: "keepalive: false to send no keepalives",
395
+ compression: "compression: false to disable compression",
396
+ control_master: "control_master: false to not use a ControlMaster",
397
+ control_path: "control_path: 'none' to not use a ControlMaster socket",
398
+ send_env: "send_env: [] to send no environment variables",
399
+ set_env: "set_env: {} to set no environment variables"
400
+ }.freeze
401
+ private_constant :NIL_ALLOWED_OPTIONS, :NIL_OPTION_HINTS
402
+
403
+ def self.reject_nil_options(options)
404
+ nil_keys = options.select do |key, value|
405
+ !NIL_ALLOWED_OPTIONS.include?(key) && (value.nil? || (value.is_a?(Array) && value.include?(nil)))
406
+ end.keys
407
+ return if nil_keys.empty?
408
+
409
+ message = "nil is not allowed for #{nil_keys.map(&:inspect).join(', ')}; " \
410
+ "omit #{nil_keys.one? ? 'it' : 'them'} to use the default or ssh_config value"
411
+ hints = NIL_OPTION_HINTS.values_at(*nil_keys).compact
412
+ message += ", or pass #{hints.join(', or ')}" if hints.any?
413
+ raise ArgumentError, message
331
414
  end
332
- private_class_method :_support_deprecated_option_paranoid
415
+ private_class_method :reject_nil_options
333
416
  end
334
417
  end
data/net-ssh.gemspec CHANGED
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
15
15
  spec.description = %q{Net::SSH: a pure-Ruby implementation of the SSH2 client protocol. It allows you to write programs that invoke and interact with processes on remote servers, via SSH2.}
16
16
  spec.homepage = "https://github.com/net-ssh/net-ssh"
17
17
  spec.license = "MIT"
18
- spec.required_ruby_version = Gem::Requirement.new(">= 2.6")
18
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.7")
19
19
  spec.metadata = {
20
20
  "changelog_uri" => "https://github.com/net-ssh/net-ssh/blob/master/CHANGES.txt"
21
21
  }
@@ -30,18 +30,15 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ["lib"]
32
32
 
33
- unless ENV['NET_SSH_NO_ED25519']
34
- spec.add_development_dependency("bcrypt_pbkdf", "~> 1.0") unless RUBY_PLATFORM == "java"
35
- spec.add_development_dependency("ed25519", "~> 1.2")
36
- spec.add_development_dependency('x25519') unless RUBY_PLATFORM == 'java'
37
- end
33
+ spec.add_dependency("logger")
34
+ spec.add_dependency("openssl", ">= 3.2.0")
38
35
 
39
- spec.add_development_dependency('rbnacl', '~> 7.1') unless ENV['NET_SSH_NO_RBNACL']
36
+ spec.add_development_dependency("bcrypt_pbkdf", "~> 1.0") unless RUBY_PLATFORM == "java"
40
37
 
41
38
  spec.add_development_dependency "base64"
42
39
  spec.add_development_dependency "bundler", ">= 1.17"
43
40
  spec.add_development_dependency "minitest", "~> 5.19"
44
- spec.add_development_dependency "mocha", "~> 2.1.0"
41
+ spec.add_development_dependency "mocha", ">= 2.1", "< 4"
45
42
  spec.add_development_dependency "ostruct"
46
43
  spec.add_development_dependency "rake", "~> 12.0"
47
44
  spec.add_development_dependency "rubocop", "~> 1.28.0"