net-ssh 4.2.0 → 7.0.1

Sign up to get free protection for your applications and to get access to all the features.
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