net-ssh-backports 6.3.0.backports

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 (111) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +93 -0
  3. data/.gitignore +13 -0
  4. data/.rubocop.yml +21 -0
  5. data/.rubocop_todo.yml +1074 -0
  6. data/.travis.yml +51 -0
  7. data/CHANGES.txt +698 -0
  8. data/Gemfile +13 -0
  9. data/Gemfile.noed25519 +12 -0
  10. data/ISSUE_TEMPLATE.md +30 -0
  11. data/LICENSE.txt +19 -0
  12. data/Manifest +132 -0
  13. data/README.md +287 -0
  14. data/Rakefile +105 -0
  15. data/THANKS.txt +110 -0
  16. data/appveyor.yml +58 -0
  17. data/lib/net/ssh/authentication/agent.rb +284 -0
  18. data/lib/net/ssh/authentication/certificate.rb +183 -0
  19. data/lib/net/ssh/authentication/constants.rb +20 -0
  20. data/lib/net/ssh/authentication/ed25519.rb +185 -0
  21. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  22. data/lib/net/ssh/authentication/key_manager.rb +297 -0
  23. data/lib/net/ssh/authentication/methods/abstract.rb +69 -0
  24. data/lib/net/ssh/authentication/methods/hostbased.rb +72 -0
  25. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +77 -0
  26. data/lib/net/ssh/authentication/methods/none.rb +34 -0
  27. data/lib/net/ssh/authentication/methods/password.rb +80 -0
  28. data/lib/net/ssh/authentication/methods/publickey.rb +95 -0
  29. data/lib/net/ssh/authentication/pageant.rb +497 -0
  30. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  31. data/lib/net/ssh/authentication/session.rb +163 -0
  32. data/lib/net/ssh/buffer.rb +434 -0
  33. data/lib/net/ssh/buffered_io.rb +202 -0
  34. data/lib/net/ssh/config.rb +406 -0
  35. data/lib/net/ssh/connection/channel.rb +695 -0
  36. data/lib/net/ssh/connection/constants.rb +33 -0
  37. data/lib/net/ssh/connection/event_loop.rb +123 -0
  38. data/lib/net/ssh/connection/keepalive.rb +59 -0
  39. data/lib/net/ssh/connection/session.rb +712 -0
  40. data/lib/net/ssh/connection/term.rb +180 -0
  41. data/lib/net/ssh/errors.rb +106 -0
  42. data/lib/net/ssh/key_factory.rb +218 -0
  43. data/lib/net/ssh/known_hosts.rb +264 -0
  44. data/lib/net/ssh/loggable.rb +62 -0
  45. data/lib/net/ssh/packet.rb +106 -0
  46. data/lib/net/ssh/prompt.rb +62 -0
  47. data/lib/net/ssh/proxy/command.rb +123 -0
  48. data/lib/net/ssh/proxy/errors.rb +16 -0
  49. data/lib/net/ssh/proxy/http.rb +98 -0
  50. data/lib/net/ssh/proxy/https.rb +50 -0
  51. data/lib/net/ssh/proxy/jump.rb +54 -0
  52. data/lib/net/ssh/proxy/socks4.rb +67 -0
  53. data/lib/net/ssh/proxy/socks5.rb +140 -0
  54. data/lib/net/ssh/service/forward.rb +426 -0
  55. data/lib/net/ssh/test/channel.rb +147 -0
  56. data/lib/net/ssh/test/extensions.rb +173 -0
  57. data/lib/net/ssh/test/kex.rb +46 -0
  58. data/lib/net/ssh/test/local_packet.rb +53 -0
  59. data/lib/net/ssh/test/packet.rb +101 -0
  60. data/lib/net/ssh/test/remote_packet.rb +40 -0
  61. data/lib/net/ssh/test/script.rb +180 -0
  62. data/lib/net/ssh/test/socket.rb +65 -0
  63. data/lib/net/ssh/test.rb +94 -0
  64. data/lib/net/ssh/transport/algorithms.rb +502 -0
  65. data/lib/net/ssh/transport/cipher_factory.rb +103 -0
  66. data/lib/net/ssh/transport/constants.rb +40 -0
  67. data/lib/net/ssh/transport/ctr.rb +115 -0
  68. data/lib/net/ssh/transport/hmac/abstract.rb +97 -0
  69. data/lib/net/ssh/transport/hmac/md5.rb +10 -0
  70. data/lib/net/ssh/transport/hmac/md5_96.rb +9 -0
  71. data/lib/net/ssh/transport/hmac/none.rb +13 -0
  72. data/lib/net/ssh/transport/hmac/ripemd160.rb +11 -0
  73. data/lib/net/ssh/transport/hmac/sha1.rb +11 -0
  74. data/lib/net/ssh/transport/hmac/sha1_96.rb +9 -0
  75. data/lib/net/ssh/transport/hmac/sha2_256.rb +11 -0
  76. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +9 -0
  77. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  78. data/lib/net/ssh/transport/hmac/sha2_512.rb +11 -0
  79. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +9 -0
  80. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  81. data/lib/net/ssh/transport/hmac.rb +47 -0
  82. data/lib/net/ssh/transport/identity_cipher.rb +57 -0
  83. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  84. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  85. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  86. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  87. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +37 -0
  88. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  89. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +122 -0
  90. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +72 -0
  91. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +11 -0
  92. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +39 -0
  93. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +21 -0
  94. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +21 -0
  95. data/lib/net/ssh/transport/kex.rb +31 -0
  96. data/lib/net/ssh/transport/key_expander.rb +30 -0
  97. data/lib/net/ssh/transport/openssl.rb +253 -0
  98. data/lib/net/ssh/transport/packet_stream.rb +280 -0
  99. data/lib/net/ssh/transport/server_version.rb +77 -0
  100. data/lib/net/ssh/transport/session.rb +354 -0
  101. data/lib/net/ssh/transport/state.rb +208 -0
  102. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  103. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  104. data/lib/net/ssh/verifiers/always.rb +58 -0
  105. data/lib/net/ssh/verifiers/never.rb +19 -0
  106. data/lib/net/ssh/version.rb +68 -0
  107. data/lib/net/ssh.rb +330 -0
  108. data/net-ssh-public_cert.pem +20 -0
  109. data/net-ssh.gemspec +44 -0
  110. data/support/ssh_tunnel_bug.rb +65 -0
  111. metadata +271 -0
@@ -0,0 +1,406 @@
1
+ module Net
2
+ module SSH
3
+ # The Net::SSH::Config class is used to parse OpenSSH configuration files,
4
+ # and translates that syntax into the configuration syntax that Net::SSH
5
+ # understands. This lets Net::SSH scripts read their configuration (to
6
+ # some extent) from OpenSSH configuration files (~/.ssh/config, /etc/ssh_config,
7
+ # and so forth).
8
+ #
9
+ # Only a subset of OpenSSH configuration options are understood:
10
+ #
11
+ # * ChallengeResponseAuthentication => maps to the :auth_methods option challenge-response (then coleasced into keyboard-interactive)
12
+ # * KbdInteractiveAuthentication => maps to the :auth_methods keyboard-interactive
13
+ # * CertificateFile => maps to the :keycerts option
14
+ # * Ciphers => maps to the :encryption option
15
+ # * Compression => :compression
16
+ # * CompressionLevel => :compression_level
17
+ # * ConnectTimeout => maps to the :timeout option
18
+ # * ForwardAgent => :forward_agent
19
+ # * GlobalKnownHostsFile => :global_known_hosts_file
20
+ # * HostBasedAuthentication => maps to the :auth_methods option
21
+ # * HostKeyAlgorithms => maps to :host_key option
22
+ # * HostKeyAlias => :host_key_alias
23
+ # * HostName => :host_name
24
+ # * IdentityFile => maps to the :keys option
25
+ # * IdentityAgent => :identity_agent
26
+ # * IdentitiesOnly => :keys_only
27
+ # * CheckHostIP => :check_host_ip
28
+ # * Macs => maps to the :hmac option
29
+ # * PasswordAuthentication => maps to the :auth_methods option password
30
+ # * Port => :port
31
+ # * PreferredAuthentications => maps to the :auth_methods option
32
+ # * ProxyCommand => maps to the :proxy option
33
+ # * ProxyJump => maps to the :proxy option
34
+ # * PubKeyAuthentication => maps to the :auth_methods option
35
+ # * RekeyLimit => :rekey_limit
36
+ # * StrictHostKeyChecking => :verify_host_key
37
+ # * User => :user
38
+ # * UserKnownHostsFile => :user_known_hosts_file
39
+ # * NumberOfPasswordPrompts => :number_of_password_prompts
40
+ # * FingerprintHash => :fingerprint_hash
41
+ #
42
+ # Note that you will never need to use this class directly--you can control
43
+ # whether the OpenSSH configuration files are read by passing the :config
44
+ # option to Net::SSH.start. (They are, by default.)
45
+ class Config
46
+ class << self
47
+ @@default_files = %w[~/.ssh/config /etc/ssh_config /etc/ssh/ssh_config]
48
+ # The following defaults follow the openssh client ssh_config defaults.
49
+ # http://lwn.net/Articles/544640/
50
+ # "hostbased" is off and "none" is not supported but we allow it since
51
+ # it's used by some clients to query the server for allowed auth methods
52
+ @@default_auth_methods = %w[none publickey password keyboard-interactive]
53
+
54
+ # Returns an array of locations of OpenSSH configuration files
55
+ # to parse by default.
56
+ def default_files
57
+ @@default_files.clone
58
+ end
59
+
60
+ def default_auth_methods
61
+ @@default_auth_methods.clone
62
+ end
63
+
64
+ # Loads the configuration data for the given +host+ from all of the
65
+ # given +files+ (defaulting to the list of files returned by
66
+ # #default_files), translates the resulting hash into the options
67
+ # recognized by Net::SSH, and returns them.
68
+ def for(host, files=expandable_default_files)
69
+ translate(files.inject({}) { |settings, file|
70
+ load(file, host, settings)
71
+ })
72
+ end
73
+
74
+ # Load the OpenSSH configuration settings in the given +file+ for the
75
+ # given +host+. If +settings+ is given, the options are merged into
76
+ # that hash, with existing values taking precedence over newly parsed
77
+ # ones. Returns a hash containing the OpenSSH options. (See
78
+ # #translate for how to convert the OpenSSH options into Net::SSH
79
+ # options.)
80
+ def load(path, host, settings={}, base_dir = nil)
81
+ file = File.expand_path(path)
82
+ base_dir ||= File.dirname(file)
83
+ return settings unless File.readable?(file)
84
+
85
+ globals = {}
86
+ block_matched = false
87
+ block_seen = false
88
+ IO.foreach(file) do |line|
89
+ next if line =~ /^\s*(?:#.*)?$/
90
+
91
+ if line =~ /^\s*(\S+)\s*=(.*)$/
92
+ key, value = $1, $2
93
+ else
94
+ key, value = line.strip.split(/\s+/, 2)
95
+ end
96
+
97
+ # silently ignore malformed entries
98
+ next if value.nil?
99
+
100
+ key.downcase!
101
+ value = unquote(value)
102
+
103
+ value = case value.strip
104
+ when /^\d+$/ then value.to_i
105
+ when /^no$/i then false
106
+ when /^yes$/i then true
107
+ else value
108
+ end
109
+
110
+ if key == 'host'
111
+ # Support "Host host1 host2 hostN".
112
+ # See http://github.com/net-ssh/net-ssh/issues#issue/6
113
+ negative_hosts, positive_hosts = value.to_s.split(/\s+/).partition { |h| h.start_with?('!') }
114
+
115
+ # Check for negative patterns first. If the host matches, that overrules any other positive match.
116
+ # The host substring code is used to strip out the starting "!" so the regexp will be correct.
117
+ negative_matched = negative_hosts.any? { |h| host =~ pattern2regex(h[1..-1]) }
118
+
119
+ if negative_matched
120
+ block_matched = false
121
+ else
122
+ block_matched = positive_hosts.any? { |h| host =~ pattern2regex(h) }
123
+ end
124
+
125
+ block_seen = true
126
+ settings[key] = host
127
+ elsif key == 'match'
128
+ block_matched = eval_match_conditions(value, host, settings)
129
+ block_seen = true
130
+ elsif !block_seen
131
+ case key
132
+ when 'identityfile', 'certificatefile'
133
+ (globals[key] ||= []) << value
134
+ when 'include'
135
+ included_file_paths(base_dir, value).each do |file_path|
136
+ globals = load(file_path, host, globals, base_dir)
137
+ end
138
+ else
139
+ globals[key] = value unless settings.key?(key)
140
+ end
141
+ elsif block_matched
142
+ case key
143
+ when 'identityfile', 'certificatefile'
144
+ (settings[key] ||= []) << value
145
+ when 'include'
146
+ included_file_paths(base_dir, value).each do |file_path|
147
+ settings = load(file_path, host, settings, base_dir)
148
+ end
149
+ else
150
+ settings[key] = value unless settings.key?(key)
151
+ end
152
+ end
153
+
154
+ # ProxyCommand and ProxyJump override each other so they need to be tracked togeather
155
+ %w[proxyjump proxycommand].each do |proxy_key|
156
+ if (proxy_value = settings.delete(proxy_key))
157
+ settings['proxy'] ||= [proxy_key, proxy_value]
158
+ end
159
+ end
160
+ end
161
+
162
+ globals.merge(settings) do |key, oldval, newval|
163
+ case key
164
+ when 'identityfile', 'certificatefile'
165
+ oldval + newval
166
+ else
167
+ newval
168
+ end
169
+ end
170
+ end
171
+
172
+ # Given a hash of OpenSSH configuration options, converts them into
173
+ # a hash of Net::SSH options. Unrecognized options are ignored. The
174
+ # +settings+ hash must have Strings for keys, all downcased, and
175
+ # the returned hash will have Symbols for keys.
176
+ def translate(settings)
177
+ auth_methods = default_auth_methods.clone
178
+ (auth_methods << 'challenge-response').uniq!
179
+ ret = settings.each_with_object({ auth_methods: auth_methods }) do |(key, value), hash|
180
+ translate_config_key(hash, key.to_sym, value, settings)
181
+ end
182
+ merge_challenge_response_with_keyboard_interactive(ret)
183
+ end
184
+
185
+ # Filters default_files down to the files that are expandable.
186
+ def expandable_default_files
187
+ default_files.keep_if do |path|
188
+ File.expand_path(path)
189
+ true
190
+ rescue ArgumentError
191
+ false
192
+ end
193
+ end
194
+
195
+ private
196
+
197
+ def translate_verify_host_key(value)
198
+ case value
199
+ when false
200
+ :never
201
+ when true
202
+ :always
203
+ when 'accept-new'
204
+ :accept_new
205
+ end
206
+ end
207
+
208
+ def translate_keepalive(hash, value)
209
+ if value && value.to_i > 0
210
+ hash[:keepalive] = true
211
+ hash[:keepalive_interval] = value.to_i
212
+ else
213
+ hash[:keepalive] = false
214
+ end
215
+ end
216
+
217
+ TRANSLATE_CONFIG_KEY_RENAME_MAP = {
218
+ bindaddress: :bind_address,
219
+ compression: :compression,
220
+ compressionlevel: :compression_level,
221
+ certificatefile: :keycerts,
222
+ connecttimeout: :timeout,
223
+ forwardagent: :forward_agent,
224
+ identitiesonly: :keys_only,
225
+ identityagent: :identity_agent,
226
+ globalknownhostsfile: :global_known_hosts_file,
227
+ hostkeyalias: :host_key_alias,
228
+ identityfile: :keys,
229
+ fingerprinthash: :fingerprint_hash,
230
+ port: :port,
231
+ user: :user,
232
+ userknownhostsfile: :user_known_hosts_file,
233
+ checkhostip: :check_host_ip
234
+ }.freeze
235
+ def translate_config_key(hash, key, value, settings)
236
+ case key
237
+ when :stricthostkeychecking
238
+ hash[:verify_host_key] = translate_verify_host_key(value)
239
+ when :ciphers
240
+ hash[:encryption] = value.split(/,/)
241
+ when :hostbasedauthentication
242
+ if value
243
+ (hash[:auth_methods] << "hostbased").uniq!
244
+ else
245
+ hash[:auth_methods].delete("hostbased")
246
+ end
247
+ when :hostkeyalgorithms
248
+ hash[:host_key] = value.split(/,/)
249
+ when :hostname
250
+ hash[:host_name] = value.gsub(/%h/, settings['host'])
251
+ when :macs
252
+ hash[:hmac] = value.split(/,/)
253
+ when :serveralivecountmax
254
+ hash[:keepalive_maxcount] = value.to_i if value
255
+ when :serveraliveinterval
256
+ translate_keepalive(hash, value)
257
+ when :passwordauthentication
258
+ if value
259
+ (hash[:auth_methods] << 'password').uniq!
260
+ else
261
+ hash[:auth_methods].delete('password')
262
+ end
263
+ when :challengeresponseauthentication
264
+ if value
265
+ (hash[:auth_methods] << 'challenge-response').uniq!
266
+ else
267
+ hash[:auth_methods].delete('challenge-response')
268
+ end
269
+ when :kbdinteractiveauthentication
270
+ if value
271
+ (hash[:auth_methods] << 'keyboard-interactive').uniq!
272
+ else
273
+ hash[:auth_methods].delete('keyboard-interactive')
274
+ end
275
+ when :preferredauthentications
276
+ hash[:auth_methods] = value.split(/,/) # TODO we should place to preferred_auth_methods rather than auth_methods
277
+ when :proxy
278
+ if (proxy = setup_proxy(*value))
279
+ hash[:proxy] = proxy
280
+ end
281
+ when :pubkeyauthentication
282
+ if value
283
+ (hash[:auth_methods] << 'publickey').uniq!
284
+ else
285
+ hash[:auth_methods].delete('publickey')
286
+ end
287
+ when :rekeylimit
288
+ hash[:rekey_limit] = interpret_size(value)
289
+ when :sendenv
290
+ multi_send_env = value.to_s.split(/\s+/)
291
+ hash[:send_env] = multi_send_env.map { |e| Regexp.new pattern2regex(e).source, false }
292
+ when :setenv
293
+ hash[:set_env] = Shellwords.split(value.to_s).map { |e| e.split '=', 2 }.to_h
294
+ when :numberofpasswordprompts
295
+ hash[:number_of_password_prompts] = value.to_i
296
+ when *TRANSLATE_CONFIG_KEY_RENAME_MAP.keys
297
+ hash[TRANSLATE_CONFIG_KEY_RENAME_MAP[key]] = value
298
+ end
299
+ end
300
+
301
+ def setup_proxy(type, value)
302
+ case type
303
+ when 'proxycommand'
304
+ if value !~ /^none$/
305
+ require 'net/ssh/proxy/command'
306
+ Net::SSH::Proxy::Command.new(value)
307
+ end
308
+ when 'proxyjump'
309
+ require 'net/ssh/proxy/jump'
310
+ Net::SSH::Proxy::Jump.new(value)
311
+ end
312
+ end
313
+
314
+ # Converts an ssh_config pattern into a regex for matching against
315
+ # host names.
316
+ def pattern2regex(pattern)
317
+ tail = pattern
318
+ prefix = String.new
319
+ while !tail.empty? do
320
+ head,sep,tail = tail.partition(/[\*\?]/)
321
+ prefix = prefix + Regexp.quote(head)
322
+ case sep
323
+ when '*'
324
+ prefix += '.*'
325
+ when '?'
326
+ prefix += '.'
327
+ when ''
328
+ else
329
+ fail "Unpexpcted sep:#{sep}"
330
+ end
331
+ end
332
+ Regexp.new("^" + prefix + "$", true)
333
+ end
334
+
335
+ # Converts the given size into an integer number of bytes.
336
+ def interpret_size(size)
337
+ case size
338
+ when /k$/i then size.to_i * 1024
339
+ when /m$/i then size.to_i * 1024 * 1024
340
+ when /g$/i then size.to_i * 1024 * 1024 * 1024
341
+ else size.to_i
342
+ end
343
+ end
344
+
345
+ def merge_challenge_response_with_keyboard_interactive(hash)
346
+ if hash[:auth_methods].include?('challenge-response')
347
+ hash[:auth_methods].delete('challenge-response')
348
+ (hash[:auth_methods] << 'keyboard-interactive').uniq!
349
+ end
350
+ hash
351
+ end
352
+
353
+ def included_file_paths(base_dir, config_paths)
354
+ tokenize_config_value(config_paths).flat_map do |path|
355
+ Dir.glob(File.expand_path(path, base_dir)).select { |f| File.file?(f) }
356
+ end
357
+ end
358
+
359
+ # Tokenize string into tokens.
360
+ # A token is a word or a quoted sequence of words, separated by whitespaces.
361
+ def tokenize_config_value(str)
362
+ str.scan(/([^"\s]+)?(?:"([^"]+)")?\s*/).map(&:join)
363
+ end
364
+
365
+ def eval_match_conditions(condition, host, settings)
366
+ # Not using `\s` for whitespace matching as canonical
367
+ # ssh_config parser implementation (OpenSSH) has specific character set.
368
+ # Ref: https://github.com/openssh/openssh-portable/blob/2581333d564d8697837729b3d07d45738eaf5a54/misc.c#L237-L239
369
+ conditions = condition.split(/[ \t\r\n]+|(?<!=)=(?!=)/).reject(&:empty?)
370
+ return true if conditions == ["all"]
371
+
372
+ conditions = conditions.each_slice(2)
373
+ condition_matches = []
374
+ conditions.each do |(kind,exprs)|
375
+ exprs = unquote(exprs)
376
+
377
+ case kind.downcase
378
+ when "all"
379
+ raise "all cannot be mixed with other conditions"
380
+ when "host"
381
+ if exprs.start_with?('!')
382
+ negated = true
383
+ exprs = exprs[1..-1]
384
+ else
385
+ negated = false
386
+ end
387
+ condition_met = false
388
+ exprs.split(",").each do |expr|
389
+ condition_met = condition_met || host =~ pattern2regex(expr)
390
+ end
391
+ condition_matches << (true && negated ^ condition_met)
392
+ # else
393
+ # warn "net-ssh: Unsupported expr in Match block: #{kind}"
394
+ end
395
+ end
396
+
397
+ !condition_matches.empty? && condition_matches.all?
398
+ end
399
+
400
+ def unquote(string)
401
+ string =~ /^"(.*)"$/ ? Regexp.last_match(1) : string
402
+ end
403
+ end
404
+ end
405
+ end
406
+ end