net-ssh 4.2.0 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.dockerignore +6 -0
  4. data/.github/config/rubocop_linter_action.yml +4 -0
  5. data/.github/workflows/ci-with-docker.yml +44 -0
  6. data/.github/workflows/ci.yml +87 -0
  7. data/.github/workflows/rubocop.yml +13 -0
  8. data/.gitignore +7 -0
  9. data/.rubocop.yml +19 -2
  10. data/.rubocop_todo.yml +619 -667
  11. data/CHANGES.txt +110 -1
  12. data/Dockerfile +27 -0
  13. data/Dockerfile.openssl3 +17 -0
  14. data/Gemfile +3 -7
  15. data/{Gemfile.norbnacl → Gemfile.noed25519} +3 -1
  16. data/Manifest +4 -5
  17. data/README.md +293 -0
  18. data/Rakefile +45 -29
  19. data/appveyor.yml +8 -6
  20. data/docker-compose.yml +23 -0
  21. data/lib/net/ssh/authentication/agent.rb +248 -223
  22. data/lib/net/ssh/authentication/certificate.rb +178 -164
  23. data/lib/net/ssh/authentication/constants.rb +17 -15
  24. data/lib/net/ssh/authentication/ed25519.rb +141 -116
  25. data/lib/net/ssh/authentication/ed25519_loader.rb +28 -28
  26. data/lib/net/ssh/authentication/key_manager.rb +79 -36
  27. data/lib/net/ssh/authentication/methods/abstract.rb +62 -47
  28. data/lib/net/ssh/authentication/methods/hostbased.rb +34 -37
  29. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +3 -3
  30. data/lib/net/ssh/authentication/methods/none.rb +16 -19
  31. data/lib/net/ssh/authentication/methods/password.rb +15 -16
  32. data/lib/net/ssh/authentication/methods/publickey.rb +96 -55
  33. data/lib/net/ssh/authentication/pageant.rb +468 -465
  34. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  35. data/lib/net/ssh/authentication/session.rb +131 -122
  36. data/lib/net/ssh/buffer.rb +385 -332
  37. data/lib/net/ssh/buffered_io.rb +150 -151
  38. data/lib/net/ssh/config.rb +316 -239
  39. data/lib/net/ssh/connection/channel.rb +635 -613
  40. data/lib/net/ssh/connection/constants.rb +29 -29
  41. data/lib/net/ssh/connection/event_loop.rb +104 -95
  42. data/lib/net/ssh/connection/keepalive.rb +55 -51
  43. data/lib/net/ssh/connection/session.rb +614 -611
  44. data/lib/net/ssh/connection/term.rb +125 -123
  45. data/lib/net/ssh/errors.rb +101 -99
  46. data/lib/net/ssh/key_factory.rb +194 -108
  47. data/lib/net/ssh/known_hosts.rb +212 -134
  48. data/lib/net/ssh/loggable.rb +50 -49
  49. data/lib/net/ssh/packet.rb +83 -79
  50. data/lib/net/ssh/prompt.rb +51 -51
  51. data/lib/net/ssh/proxy/command.rb +105 -91
  52. data/lib/net/ssh/proxy/errors.rb +12 -10
  53. data/lib/net/ssh/proxy/http.rb +81 -81
  54. data/lib/net/ssh/proxy/https.rb +37 -36
  55. data/lib/net/ssh/proxy/jump.rb +49 -48
  56. data/lib/net/ssh/proxy/socks4.rb +2 -6
  57. data/lib/net/ssh/proxy/socks5.rb +14 -17
  58. data/lib/net/ssh/service/forward.rb +365 -362
  59. data/lib/net/ssh/test/channel.rb +145 -143
  60. data/lib/net/ssh/test/extensions.rb +131 -127
  61. data/lib/net/ssh/test/kex.rb +34 -32
  62. data/lib/net/ssh/test/local_packet.rb +46 -44
  63. data/lib/net/ssh/test/packet.rb +87 -84
  64. data/lib/net/ssh/test/remote_packet.rb +32 -30
  65. data/lib/net/ssh/test/script.rb +155 -155
  66. data/lib/net/ssh/test/socket.rb +49 -48
  67. data/lib/net/ssh/test.rb +82 -80
  68. data/lib/net/ssh/transport/algorithms.rb +433 -364
  69. data/lib/net/ssh/transport/cipher_factory.rb +95 -91
  70. data/lib/net/ssh/transport/constants.rb +32 -24
  71. data/lib/net/ssh/transport/ctr.rb +37 -15
  72. data/lib/net/ssh/transport/hmac/abstract.rb +81 -63
  73. data/lib/net/ssh/transport/hmac/md5.rb +0 -2
  74. data/lib/net/ssh/transport/hmac/md5_96.rb +0 -2
  75. data/lib/net/ssh/transport/hmac/none.rb +0 -2
  76. data/lib/net/ssh/transport/hmac/ripemd160.rb +0 -2
  77. data/lib/net/ssh/transport/hmac/sha1.rb +0 -2
  78. data/lib/net/ssh/transport/hmac/sha1_96.rb +0 -2
  79. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  80. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  81. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  82. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  83. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  84. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  85. data/lib/net/ssh/transport/hmac.rb +14 -12
  86. data/lib/net/ssh/transport/identity_cipher.rb +54 -52
  87. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  88. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  89. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  90. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  91. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
  92. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  93. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +112 -217
  94. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -63
  95. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  96. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
  97. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
  98. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
  99. data/lib/net/ssh/transport/kex.rb +15 -12
  100. data/lib/net/ssh/transport/key_expander.rb +24 -21
  101. data/lib/net/ssh/transport/openssl.rb +158 -133
  102. data/lib/net/ssh/transport/packet_stream.rb +223 -191
  103. data/lib/net/ssh/transport/server_version.rb +55 -56
  104. data/lib/net/ssh/transport/session.rb +306 -259
  105. data/lib/net/ssh/transport/state.rb +178 -176
  106. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  107. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  108. data/lib/net/ssh/verifiers/always.rb +58 -0
  109. data/lib/net/ssh/verifiers/never.rb +19 -0
  110. data/lib/net/ssh/version.rb +55 -53
  111. data/lib/net/ssh.rb +47 -34
  112. data/net-ssh-public_cert.pem +18 -19
  113. data/net-ssh.gemspec +12 -11
  114. data/support/ssh_tunnel_bug.rb +5 -5
  115. data.tar.gz.sig +0 -0
  116. metadata +78 -73
  117. metadata.gz.sig +0 -0
  118. data/.travis.yml +0 -51
  119. data/Gemfile.norbnacl.lock +0 -41
  120. data/README.rdoc +0 -169
  121. data/lib/net/ssh/ruby_compat.rb +0 -24
  122. data/lib/net/ssh/verifiers/lenient.rb +0 -30
  123. data/lib/net/ssh/verifiers/null.rb +0 -12
  124. data/lib/net/ssh/verifiers/secure.rb +0 -52
  125. data/lib/net/ssh/verifiers/strict.rb +0 -24
  126. data/support/arcfour_check.rb +0 -20
@@ -1,153 +1,167 @@
1
- module Net; module SSH
2
-
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
- # * Ciphers => maps to the :encryption option
14
- # * Compression => :compression
15
- # * CompressionLevel => :compression_level
16
- # * ConnectTimeout => maps to the :timeout option
17
- # * ForwardAgent => :forward_agent
18
- # * GlobalKnownHostsFile => :global_known_hosts_file
19
- # * HostBasedAuthentication => maps to the :auth_methods option
20
- # * HostKeyAlgorithms => maps to :host_key option
21
- # * HostKeyAlias => :host_key_alias
22
- # * HostName => :host_name
23
- # * IdentityFile => maps to the :keys option
24
- # * IdentitiesOnly => :keys_only
25
- # * Macs => maps to the :hmac option
26
- # * PasswordAuthentication => maps to the :auth_methods option password
27
- # * Port => :port
28
- # * PreferredAuthentications => maps to the :auth_methods option
29
- # * ProxyCommand => maps to the :proxy option
30
- # * ProxyJump => maps to the :proxy option
31
- # * PubKeyAuthentication => maps to the :auth_methods option
32
- # * RekeyLimit => :rekey_limit
33
- # * User => :user
34
- # * UserKnownHostsFile => :user_known_hosts_file
35
- # * NumberOfPasswordPrompts => :number_of_password_prompts
36
- #
37
- # Note that you will never need to use this class directly--you can control
38
- # whether the OpenSSH configuration files are read by passing the :config
39
- # option to Net::SSH.start. (They are, by default.)
40
- class Config
41
- class << self
42
- @@default_files = %w(~/.ssh/config /etc/ssh_config /etc/ssh/ssh_config)
43
- # The following defaults follow the openssh client ssh_config defaults.
44
- # http://lwn.net/Articles/544640/
45
- # "hostbased" is off and "none" is not supported but we allow it since
46
- # it's used by some clients to query the server for allowed auth methods
47
- @@default_auth_methods = %w(none publickey password keyboard-interactive)
48
-
49
- # Returns an array of locations of OpenSSH configuration files
50
- # to parse by default.
51
- def default_files
52
- @@default_files
53
- end
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]
54
53
 
55
- def default_auth_methods
56
- @@default_auth_methods
57
- end
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
58
59
 
59
- # Loads the configuration data for the given +host+ from all of the
60
- # given +files+ (defaulting to the list of files returned by
61
- # #default_files), translates the resulting hash into the options
62
- # recognized by Net::SSH, and returns them.
63
- def for(host, files=expandable_default_files)
64
- translate(files.inject({}) { |settings, file|
65
- load(file, host, settings)
66
- })
67
- end
60
+ def default_auth_methods
61
+ @@default_auth_methods.clone
62
+ end
68
63
 
69
- # Load the OpenSSH configuration settings in the given +file+ for the
70
- # given +host+. If +settings+ is given, the options are merged into
71
- # that hash, with existing values taking precedence over newly parsed
72
- # ones. Returns a hash containing the OpenSSH options. (See
73
- # #translate for how to convert the OpenSSH options into Net::SSH
74
- # options.)
75
- def load(path, host, settings={}, base_dir = nil)
76
- file = File.expand_path(path)
77
- base_dir ||= File.dirname(file)
78
- return settings unless File.readable?(file)
79
-
80
- globals = {}
81
- host_matched = false
82
- seen_host = false
83
- IO.foreach(file) do |line|
84
- next if line =~ /^\s*(?:#.*)?$/
85
-
86
- if line =~ /^\s*(\S+)\s*=(.*)$/
87
- key, value = $1, $2
88
- else
89
- key, value = line.strip.split(/\s+/, 2)
90
- end
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
91
73
 
92
- # silently ignore malformed entries
93
- next if value.nil?
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)
94
84
 
95
- key.downcase!
96
- value = $1 if value =~ /^"(.*)"$/
85
+ globals = {}
86
+ block_matched = false
87
+ block_seen = false
88
+ IO.foreach(file) do |line|
89
+ next if line =~ /^\s*(?:#.*)?$/
97
90
 
98
- value = case value.strip
99
- when /^\d+$/ then value.to_i
100
- when /^no$/i then false
101
- when /^yes$/i then true
102
- else value
91
+ if line =~ /^\s*(\S+)\s*=(.*)$/
92
+ key, value = $1, $2
93
+ else
94
+ key, value = line.strip.split(/\s+/, 2)
103
95
  end
104
96
 
105
- if key == 'host'
106
- # Support "Host host1 host2 hostN".
107
- # See http://github.com/net-ssh/net-ssh/issues#issue/6
108
- negative_hosts, positive_hosts = value.to_s.split(/\s+/).partition { |h| h.start_with?('!') }
97
+ # silently ignore malformed entries
98
+ next if value.nil?
109
99
 
110
- # Check for negative patterns first. If the host matches, that overrules any other positive match.
111
- # The host substring code is used to strip out the starting "!" so the regexp will be correct.
112
- negative_matched = negative_hosts.any? { |h| host =~ pattern2regex(h[1..-1]) }
100
+ key.downcase!
101
+ value = unquote(value)
113
102
 
114
- if negative_matched
115
- host_matched = false
116
- else
117
- host_matched = positive_hosts.any? { |h| host =~ pattern2regex(h) }
118
- end
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
119
109
 
120
- seen_host = true
121
- settings[key] = host
122
- elsif !seen_host
123
- case key
124
- when 'identityfile'
125
- (globals[key] ||= []) << value
126
- when 'include'
127
- included_file_paths(base_dir, value).each do |file_path|
128
- globals = load(file_path, host, globals, base_dir)
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)
129
151
  end
130
- else
131
- globals[key] = value unless settings.key?(key)
132
152
  end
133
- elsif host_matched
134
- case key
135
- when 'identityfile'
136
- (settings[key] ||= []) << value
137
- when 'include'
138
- included_file_paths(base_dir, value).each do |file_path|
139
- settings = load(file_path, host, settings, base_dir)
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]
140
158
  end
141
- else
142
- settings[key] = value unless settings.key?(key)
143
159
  end
144
160
  end
145
- end
146
161
 
147
- if globals
148
- settings = globals.merge(settings) do |key, oldval, newval|
162
+ globals.merge(settings) do |key, oldval, newval|
149
163
  case key
150
- when 'identityfile'
164
+ when 'identityfile', 'certificatefile'
151
165
  oldval + newval
152
166
  else
153
167
  newval
@@ -155,121 +169,145 @@ module Net; module SSH
155
169
  end
156
170
  end
157
171
 
158
- return settings
159
- end
160
-
161
- # Given a hash of OpenSSH configuration options, converts them into
162
- # a hash of Net::SSH options. Unrecognized options are ignored. The
163
- # +settings+ hash must have Strings for keys, all downcased, and
164
- # the returned hash will have Symbols for keys.
165
- def translate(settings)
166
- auth_methods = default_auth_methods.clone
167
- (auth_methods << 'challenge-response').uniq!
168
- ret = settings.inject({auth_methods: auth_methods}) do |hash, (key, value)|
169
- translate_config_key(hash, key.to_sym, value, settings)
170
- hash
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)
171
183
  end
172
- merge_challenge_response_with_keyboard_interactive(ret)
173
- end
174
184
 
175
- # Filters default_files down to the files that are expandable.
176
- def expandable_default_files
177
- default_files.keep_if do |path|
178
- begin
185
+ # Filters default_files down to the files that are expandable.
186
+ def expandable_default_files
187
+ default_files.keep_if do |path|
179
188
  File.expand_path(path)
180
189
  true
181
190
  rescue ArgumentError
182
191
  false
183
192
  end
184
193
  end
185
- end
186
194
 
187
- private
195
+ private
188
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
189
235
  def translate_config_key(hash, key, value, settings)
190
- rename = {
191
- bindaddress: :bind_address,
192
- compression: :compression,
193
- compressionlevel: :compression_level,
194
- connecttimeout: :timeout,
195
- forwardagent: :forward_agent,
196
- identitiesonly: :keys_only,
197
- globalknownhostsfile: :global_known_hosts_file,
198
- hostkeyalias: :host_key_alias,
199
- identityfile: :keys,
200
- port: :port,
201
- user: :user,
202
- userknownhostsfile: :user_known_hosts_file
203
- }
204
236
  case key
205
- when :ciphers
206
- hash[:encryption] = value.split(/,/)
207
- when :hostbasedauthentication
208
- if value
209
- (hash[:auth_methods] << "hostbased").uniq!
210
- else
211
- hash[:auth_methods].delete("hostbased")
212
- end
213
- when :hostkeyalgorithms
214
- hash[:host_key] = value.split(/,/)
215
- when :hostname
216
- hash[:host_name] = value.gsub(/%h/, settings['host'])
217
- when :macs
218
- hash[:hmac] = value.split(/,/)
219
- when :serveralivecountmax
220
- hash[:keepalive_maxcount] = value.to_i if value
221
- when :serveraliveinterval
222
- if value && value.to_i > 0
223
- hash[:keepalive] = true
224
- hash[:keepalive_interval] = value.to_i
225
- else
226
- hash[:keepalive] = false
227
- end
228
- when :passwordauthentication
229
- if value
230
- (hash[:auth_methods] << 'password').uniq!
231
- else
232
- hash[:auth_methods].delete('password')
233
- end
234
- when :challengeresponseauthentication
235
- if value
236
- (hash[:auth_methods] << 'challenge-response').uniq!
237
- else
238
- hash[:auth_methods].delete('challenge-response')
239
- end
240
- when :kbdinteractiveauthentication
241
- if value
242
- (hash[:auth_methods] << 'keyboard-interactive').uniq!
243
- else
244
- hash[:auth_methods].delete('keyboard-interactive')
245
- end
246
- when :preferredauthentications
247
- hash[:auth_methods] = value.split(/,/) # TODO we should place to preferred_auth_methods rather than auth_methods
248
- when :proxycommand
249
- if value and !(value =~ /^none$/)
250
- require 'net/ssh/proxy/command'
251
- hash[:proxy] = Net::SSH::Proxy::Command.new(value)
252
- end
253
- when :proxyjump
254
- if value
255
- require 'net/ssh/proxy/jump'
256
- hash[:proxy] = Net::SSH::Proxy::Jump.new(value)
257
- end
258
- when :pubkeyauthentication
259
- if value
260
- (hash[:auth_methods] << 'publickey').uniq!
261
- else
262
- hash[:auth_methods].delete('publickey')
263
- end
264
- when :rekeylimit
265
- hash[:rekey_limit] = interpret_size(value)
266
- when :sendenv
267
- multi_send_env = value.to_s.split(/\s+/)
268
- hash[:send_env] = multi_send_env.map { |e| Regexp.new pattern2regex(e).source, false }
269
- when :numberofpasswordprompts
270
- hash[:number_of_password_prompts] = value.to_i
271
- when *rename.keys
272
- hash[rename[key]] = value
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)
273
311
  end
274
312
  end
275
313
 
@@ -277,9 +315,9 @@ module Net; module SSH
277
315
  # host names.
278
316
  def pattern2regex(pattern)
279
317
  tail = pattern
280
- prefix = ""
318
+ prefix = String.new
281
319
  while !tail.empty? do
282
- head,sep,tail = tail.partition(/[\*\?]/)
320
+ head, sep, tail = tail.partition(/[\*\?]/)
283
321
  prefix = prefix + Regexp.quote(head)
284
322
  case sep
285
323
  when '*'
@@ -323,7 +361,46 @@ module Net; module SSH
323
361
  def tokenize_config_value(str)
324
362
  str.scan(/([^"\s]+)?(?:"([^"]+)")?\s*/).map(&:join)
325
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
326
404
  end
327
405
  end
328
-
329
- end; end
406
+ end