net-ssh 2.7.0 → 2.8.0

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.
@@ -1,5 +1,22 @@
1
1
 
2
2
 
3
+ === 2.8.0 / 01 Feb 2014
4
+
5
+ * Handle ssh-rsa and ssh-dss certificate files [bobveznat]
6
+ * Correctly interpret /etc/ssh_config Authentication settings based on openssh /etc/ssh_config system defaults [therealjessesanford, liggitt]
7
+ * Fixed pageant support for Windows [jarredholman]
8
+ * Support %r in ProxyCommand configuration in ssh_config files as defined in OpenSSH [yugui]
9
+ * Don't use ssh-agent if :keys_only is true [SFEley]
10
+ * Fix the bug in keys with comments [bobtfish]
11
+ * Add a failing tests for options in pub keys [bobtfish]
12
+ * Assert that the return value from ssh block is returned [carlhoerberg]
13
+ * Don't close the connection it's already closed [carlhoerberg]
14
+ * Ensure the connection closes even on exception [carlhoerberg]
15
+ * Make the authentication error message more useful [deric]
16
+ * Fix "ConnectionError" typo in lib/net/ssh/proxy/socks5.rb [mirakui]
17
+ * Allow KeyManager to recover from incompatible agents [ecki, delano]
18
+ * Fix for "Authentication Method determination can pick up a class from the root namespace" [dave.sieh]
19
+
3
20
  === 2.7.0 / 11 Sep 2013
4
21
 
5
22
  * Fix for 'Could not parse PKey: no start line' error on private keys with passphrases (issue #101) [metametaclass]
data/THANKS.txt CHANGED
@@ -19,6 +19,30 @@ Chris Andrews <chris@nodnol.org> and Lee Jensen <lee@outerim.com>
19
19
  Hiroshi Nakamura
20
20
  * fixed errors with JRuby tests
21
21
 
22
+ bobveznat
23
+ therealjessesanford
24
+ liggitt
25
+ jarredholman
26
+ yugui
27
+ SFEley
28
+ bobtfish
29
+ carlhoerberg
30
+ deric
31
+ mirakui
32
+ ecki
33
+ Dave Sieh
34
+ metametaclass
35
+ fnordfish
36
+ krishicks
37
+ noric
38
+ GabKlein
39
+ Josh Kalderimis
40
+ voxik
41
+ Olipro
42
+ jansegre
43
+ priteau
44
+ jordimassaguerpla
45
+ Kenichi Kamiya
22
46
  Andreas Wolff
23
47
  mhuffnagle
24
48
  ohrite
@@ -83,3 +107,4 @@ watsonian
83
107
  Grant Hutchins
84
108
  Michael Schubert
85
109
  mtrudel
110
+ Aurélien Derouineau
@@ -204,15 +204,17 @@ module Net
204
204
  if auth.authenticate("ssh-connection", user, options[:password])
205
205
  connection = Connection::Session.new(transport, options)
206
206
  if block_given?
207
- retval = yield connection
208
- connection.close
209
- retval
207
+ begin
208
+ yield connection
209
+ ensure
210
+ connection.close unless connection.closed?
211
+ end
210
212
  else
211
213
  return connection
212
214
  end
213
215
  else
214
216
  transport.close
215
- raise AuthenticationFailed, user
217
+ raise AuthenticationFailed, "Authentication failed for user #{user}@#{host}"
216
218
  end
217
219
  end
218
220
 
@@ -76,9 +76,9 @@ module Net; module SSH; module Authentication
76
76
  type, body = send_and_wait(SSH2_AGENT_REQUEST_VERSION, :string, Transport::ServerVersion::PROTO_VERSION)
77
77
 
78
78
  if type == SSH2_AGENT_VERSION_RESPONSE
79
- raise NotImplementedError, "SSH2 agents are not yet supported"
79
+ raise AgentNotAvailable, "SSH2 agents are not yet supported"
80
80
  elsif type != SSH_AGENT_RSA_IDENTITIES_ANSWER1 && type != SSH_AGENT_RSA_IDENTITIES_ANSWER2
81
- raise AgentError, "unknown response from agent: #{type}, #{body.to_s.inspect}"
81
+ raise AgentNotAvailable, "unknown response from agent: #{type}, #{body.to_s.inspect}"
82
82
  end
83
83
  end
84
84
 
@@ -126,7 +126,7 @@ module Net; module SSH; module Authentication
126
126
  # Returns the agent socket factory to use.
127
127
  def agent_socket_factory
128
128
  if Net::SSH::Authentication::PLATFORM == :win32
129
- Pageant::socket_factory
129
+ Pageant::Socket
130
130
  else
131
131
  UNIXSocket
132
132
  end
@@ -12,7 +12,7 @@ module Net
12
12
 
13
13
  # This class encapsulates all operations done by clients on a user's
14
14
  # private keys. In practice, the client should never need a reference
15
- # to a private key; instead, they grab a list of "identities" (public
15
+ # to a private key; instead, they grab a list of "identities" (public
16
16
  # keys) that are available from the KeyManager, and then use
17
17
  # the KeyManager to do various private key operations using those
18
18
  # identities.
@@ -37,12 +37,13 @@ module Net
37
37
  attr_reader :options
38
38
 
39
39
  # Create a new KeyManager. By default, the manager will
40
- # use the ssh-agent (if it is running).
40
+ # use the ssh-agent if it is running and the `:keys_only` option
41
+ # is not true.
41
42
  def initialize(logger, options={})
42
43
  self.logger = logger
43
44
  @key_files = []
44
45
  @key_data = []
45
- @use_agent = true
46
+ @use_agent = !options[:keys_only]
46
47
  @known_identities = {}
47
48
  @agent = nil
48
49
  @options = options
@@ -91,9 +92,8 @@ module Net
91
92
  # ssh-agent. Note that identities from an ssh-agent are always listed
92
93
  # first in the array, with other identities coming after.
93
94
  #
94
- # If key manager was created with :keys_only option, any identity
95
- # from ssh-agent will be ignored unless it present in key_files or
96
- # key_data.
95
+ # If key manager was created with :keys_only option, no identities
96
+ # from ssh-agent will be loaded.
97
97
  def each_identity
98
98
  prepared_identities = prepare_identities_from_files + prepare_identities_from_data
99
99
 
@@ -139,7 +139,7 @@ module Net
139
139
  if info[:key].nil? && info[:from] == :file
140
140
  begin
141
141
  info[:key] = KeyFactory.load_private_key(info[:file], options[:passphrase], true)
142
- rescue Exception, OpenSSL::OpenSSLError => e
142
+ rescue Exception, OpenSSL::OpenSSLError => e
143
143
  raise KeyManagerError, "the given identity is known, but the private key could not be loaded: #{e.class} (#{e.message})"
144
144
  end
145
145
  end
@@ -110,21 +110,49 @@ module Net; module SSH; module Authentication
110
110
  "pageant process not running"
111
111
  end
112
112
 
113
- @res = nil
114
- @pos = 0
113
+ @input_buffer = Net::SSH::Buffer.new
114
+ @output_buffer = Net::SSH::Buffer.new
115
115
  end
116
116
 
117
117
  # Forwards the data to #send_query, ignoring any arguments after
118
- # the first. Returns 0.
118
+ # the first.
119
119
  def send(data, *args)
120
- @res = send_query(data)
121
- @pos = 0
120
+ @input_buffer.append(data)
121
+
122
+ ret = data.length
123
+
124
+ while true
125
+ return ret if @input_buffer.length < 4
126
+ msg_length = @input_buffer.read_long + 4
127
+ @input_buffer.reset!
128
+
129
+ return ret if @input_buffer.length < msg_length
130
+ msg = @input_buffer.read!(msg_length)
131
+ @output_buffer.append(send_query(msg))
132
+ end
133
+ end
134
+
135
+ # Reads +n+ bytes from the cached result of the last query. If +n+
136
+ # is +nil+, returns all remaining data from the last query.
137
+ def read(n = nil)
138
+ @output_buffer.read(n)
122
139
  end
123
140
 
141
+ def close
142
+ end
143
+
144
+ def send_query(query)
145
+ if RUBY_VERSION < "1.9"
146
+ send_query_18(query)
147
+ else
148
+ send_query_19(query)
149
+ end
150
+ end
151
+
124
152
  # Packages the given query string and sends it to the pageant
125
153
  # process via the Windows messaging subsystem. The result is
126
154
  # cached, to be returned piece-wise when #read is called.
127
- def send_query(query)
155
+ def send_query_18(query)
128
156
  res = nil
129
157
  filemap = 0
130
158
  ptr = nil
@@ -165,43 +193,10 @@ module Net; module SSH; module Authentication
165
193
  Win.closeHandle(filemap) if filemap != 0
166
194
  end
167
195
 
168
- # Conceptually close the socket. This doesn't really do anthing
169
- # significant, but merely complies with the Socket interface.
170
- def close
171
- @res = nil
172
- @pos = 0
173
- end
174
-
175
- # Conceptually asks if the socket is closed. As with #close,
176
- # this doesn't really do anything significant, but merely
177
- # complies with the Socket interface.
178
- def closed?
179
- @res.nil? && @pos.zero?
180
- end
181
-
182
- # Reads +n+ bytes from the cached result of the last query. If +n+
183
- # is +nil+, returns all remaining data from the last query.
184
- def read(n = nil)
185
- return nil unless @res
186
- if n.nil?
187
- start, @pos = @pos, @res.size
188
- return @res[start..-1]
189
- else
190
- start, @pos = @pos, @pos + n
191
- return @res[start, n]
192
- end
193
- end
194
-
195
- end
196
-
197
- # Socket changes for Ruby 1.9
198
- # Functionality is the same as Ruby 1.8 but it includes the new calls to
199
- # the DL module as well as other pointer transformations
200
- class Socket19 < Socket
201
196
  # Packages the given query string and sends it to the pageant
202
197
  # process via the Windows messaging subsystem. The result is
203
198
  # cached, to be returned piece-wise when #read is called.
204
- def send_query(query)
199
+ def send_query_19(query)
205
200
  res = nil
206
201
  filemap = 0
207
202
  ptr = nil
@@ -244,17 +239,6 @@ module Net; module SSH; module Authentication
244
239
  Win.CloseHandle(filemap) if filemap != 0
245
240
  end
246
241
  end
247
-
248
- # Selects which socket to use depending on the ruby version
249
- # This is needed due changes in the DL module.
250
- def self.socket_factory
251
- if RUBY_VERSION < "1.9"
252
- Socket
253
- else
254
- Socket19
255
- end
256
- end
257
-
258
242
  end
259
243
 
260
244
  end; end; end
@@ -2,6 +2,7 @@ require 'net/ssh/loggable'
2
2
  require 'net/ssh/transport/constants'
3
3
  require 'net/ssh/authentication/constants'
4
4
  require 'net/ssh/authentication/key_manager'
5
+ require 'net/ssh/authentication/methods/none'
5
6
  require 'net/ssh/authentication/methods/publickey'
6
7
  require 'net/ssh/authentication/methods/hostbased'
7
8
  require 'net/ssh/authentication/methods/password'
@@ -41,7 +42,7 @@ module Net; module SSH; module Authentication
41
42
  self.logger = transport.logger
42
43
  @transport = transport
43
44
 
44
- @auth_methods = options[:auth_methods] || %w(none publickey hostbased password keyboard-interactive)
45
+ @auth_methods = options[:auth_methods] || Net::SSH::Config.default_auth_methods
45
46
  @options = options
46
47
 
47
48
  @allowed_auth_methods = @auth_methods
@@ -243,14 +243,14 @@ module Net; module SSH
243
243
  # a key. Only RSA, DSA, and ECDSA keys are supported.
244
244
  def read_keyblob(type)
245
245
  case type
246
- when "ssh-dss"
246
+ when /^ssh-dss(-cert-v01@openssh\.com)?$/
247
247
  key = OpenSSL::PKey::DSA.new
248
248
  key.p = read_bignum
249
249
  key.q = read_bignum
250
250
  key.g = read_bignum
251
251
  key.pub_key = read_bignum
252
252
 
253
- when "ssh-rsa"
253
+ when /^ssh-rsa(-cert-v01@openssh\.com)?$/
254
254
  key = OpenSSL::PKey::RSA.new
255
255
  key.e = read_bignum
256
256
  key.n = read_bignum
@@ -8,6 +8,7 @@ module Net; module SSH
8
8
  #
9
9
  # Only a subset of OpenSSH configuration options are understood:
10
10
  #
11
+ # * ChallengeResponseAuthentication => maps to the :auth_methods option
11
12
  # * Ciphers => maps to the :encryption option
12
13
  # * Compression => :compression
13
14
  # * CompressionLevel => :compression_level
@@ -25,6 +26,7 @@ module Net; module SSH
25
26
  # * Port => :port
26
27
  # * PreferredAuthentications => maps to the :auth_methods option
27
28
  # * ProxyCommand => maps to the :proxy option
29
+ # * PubKeyAuthentication => maps to the :auth_methods option
28
30
  # * RekeyLimit => :rekey_limit
29
31
  # * User => :user
30
32
  # * UserKnownHostsFile => :user_known_hosts_file
@@ -35,19 +37,30 @@ module Net; module SSH
35
37
  class Config
36
38
  class << self
37
39
  @@default_files = %w(~/.ssh/config /etc/ssh_config /etc/ssh/ssh_config)
40
+ # The following defaults follow the openssh client ssh_config defaults.
41
+ # http://lwn.net/Articles/544640/
42
+ # "hostbased" is off and "none" is not supported but we allow it since
43
+ # it's used by some clients to query the server for allowed auth methods
44
+ @@default_auth_methods = %w(none publickey password keyboard-interactive)
38
45
 
39
46
  # Returns an array of locations of OpenSSH configuration files
40
47
  # to parse by default.
41
48
  def default_files
42
49
  @@default_files
43
50
  end
51
+
52
+ def default_auth_methods
53
+ @@default_auth_methods
54
+ end
44
55
 
45
56
  # Loads the configuration data for the given +host+ from all of the
46
57
  # given +files+ (defaulting to the list of files returned by
47
58
  # #default_files), translates the resulting hash into the options
48
59
  # recognized by Net::SSH, and returns them.
49
60
  def for(host, files=default_files)
50
- translate(files.inject({}) { |settings, file| load(file, host, settings) })
61
+ hash = translate(files.inject({}) { |settings, file|
62
+ load(file, host, settings)
63
+ })
51
64
  end
52
65
 
53
66
  # Load the OpenSSH configuration settings in the given +file+ for the
@@ -59,6 +72,8 @@ module Net; module SSH
59
72
  def load(path, host, settings={})
60
73
  file = File.expand_path(path)
61
74
  return settings unless File.readable?(file)
75
+
76
+ settings[:auth_methods] ||= default_auth_methods.clone
62
77
 
63
78
  globals = {}
64
79
  matched_host = nil
@@ -119,6 +134,7 @@ module Net; module SSH
119
134
  # the returned hash will have Symbols for keys.
120
135
  def translate(settings)
121
136
  settings.inject({}) do |hash, (key, value)|
137
+ hash[:auth_methods] ||= settings[:auth_methods] || default_auth_methods.clone
122
138
  case key
123
139
  when 'bindaddress' then
124
140
  hash[:bind_address] = value
@@ -138,8 +154,9 @@ module Net; module SSH
138
154
  hash[:global_known_hosts_file] = value
139
155
  when 'hostbasedauthentication' then
140
156
  if value
141
- hash[:auth_methods] ||= []
142
- hash[:auth_methods] << "hostbased"
157
+ (hash[:auth_methods] << "hostbased").uniq!
158
+ else
159
+ hash[:auth_methods].delete("hostbased")
143
160
  end
144
161
  when 'hostkeyalgorithms' then
145
162
  hash[:host_key] = value.split(/,/)
@@ -153,8 +170,15 @@ module Net; module SSH
153
170
  hash[:hmac] = value.split(/,/)
154
171
  when 'passwordauthentication'
155
172
  if value
156
- hash[:auth_methods] ||= []
157
- hash[:auth_methods] << "password"
173
+ (hash[:auth_methods] << 'password').uniq!
174
+ else
175
+ hash[:auth_methods].delete('password')
176
+ end
177
+ when 'challengeresponseauthentication'
178
+ if value
179
+ (hash[:auth_methods] << 'keyboard-interactive').uniq!
180
+ else
181
+ hash[:auth_methods].delete('keyboard-interactive')
158
182
  end
159
183
  when 'port'
160
184
  hash[:port] = value
@@ -165,10 +189,11 @@ module Net; module SSH
165
189
  require 'net/ssh/proxy/command'
166
190
  hash[:proxy] = Net::SSH::Proxy::Command.new(value)
167
191
  end
168
- when 'pubkeyauthentication'
192
+ when 'pubkeyauthentication'
169
193
  if value
170
- hash[:auth_methods] ||= []
171
- hash[:auth_methods] << "publickey"
194
+ (hash[:auth_methods] << 'publickey').uniq!
195
+ else
196
+ hash[:auth_methods].delete('publickey')
172
197
  end
173
198
  when 'rekeylimit'
174
199
  hash[:rekey_limit] = interpret_size(value)
@@ -105,7 +105,13 @@ module Net; module SSH
105
105
  # the file describes an RSA or DSA key, and will load it
106
106
  # appropriately. The new public key is returned.
107
107
  def load_data_public_key(data, filename="")
108
- _, blob = data.split(/ /)
108
+ fields = data.split(/ /)
109
+
110
+ blob = nil
111
+ begin
112
+ blob = fields.shift
113
+ end while !blob.nil? && !/^(ssh-(rsa|dss)|ecdsa-sha2-nistp\d+)$/.match(blob)
114
+ blob = fields.shift
109
115
 
110
116
  raise Net::SSH::Exception, "public key at #{filename} is not valid" if blob.nil?
111
117
 
@@ -33,13 +33,20 @@ module Net; module SSH; module Proxy
33
33
 
34
34
  # Return a new socket connected to the given host and port via the
35
35
  # proxy that was requested when the socket factory was instantiated.
36
- def open(host, port)
36
+ def open(host, port, connection_options = nil)
37
37
  command_line = @command_line_template.gsub(/%(.)/) {
38
38
  case $1
39
39
  when 'h'
40
40
  host
41
41
  when 'p'
42
42
  port.to_s
43
+ when 'r'
44
+ remote_user = connection_options && connection_options[:remote_user]
45
+ if remote_user
46
+ remote_user
47
+ else
48
+ raise ArgumentError, "remote user name not available"
49
+ end
43
50
  when '%'
44
51
  '%'
45
52
  else
@@ -48,7 +48,7 @@ module Net; module SSH; module Proxy
48
48
 
49
49
  # Return a new socket connected to the given host and port via the
50
50
  # proxy that was requested when the socket factory was instantiated.
51
- def open(host, port)
51
+ def open(host, port, connection_options = nil)
52
52
  socket = TCPSocket.new(proxy_host, proxy_port)
53
53
  socket.write "CONNECT #{host}:#{port} HTTP/1.0\r\n"
54
54
 
@@ -47,7 +47,7 @@ module Net
47
47
 
48
48
  # Return a new socket connected to the given host and port via the
49
49
  # proxy that was requested when the socket factory was instantiated.
50
- def open(host, port)
50
+ def open(host, port, connection_options)
51
51
  socket = TCPSocket.new(proxy_host, proxy_port)
52
52
  ip_addr = IPAddr.new(Resolv.getaddress(host))
53
53
 
@@ -62,7 +62,7 @@ module Net
62
62
 
63
63
  # Return a new socket connected to the given host and port via the
64
64
  # proxy that was requested when the socket factory was instantiated.
65
- def open(host, port)
65
+ def open(host, port, connection_options = nil)
66
66
  socket = TCPSocket.new(proxy_host, proxy_port)
67
67
 
68
68
  methods = [METHOD_NO_AUTH]
@@ -108,7 +108,7 @@ module Net
108
108
  ipv6addr hostname = socket.recv(16)
109
109
  else
110
110
  socket.close
111
- raise ConnectionError, "Illegal response type"
111
+ raise ConnectError, "Illegal response type"
112
112
  end
113
113
  portnum = socket.recv(2)
114
114
 
@@ -47,8 +47,12 @@ module Net; module SSH; module Service
47
47
  # If three arguments are given, it is as if the local bind address is
48
48
  # "127.0.0.1", and the rest are applied as above.
49
49
  #
50
+ # To request an ephemeral port on the remote server, provide 0 (zero) for
51
+ # the port number. In all cases, this method will return the port that
52
+ # has been assigned.
53
+ #
50
54
  # ssh.forward.local(1234, "www.capify.org", 80)
51
- # ssh.forward.local("0.0.0.0", 1234, "www.capify.org", 80)
55
+ # assigned_port = ssh.forward.local("0.0.0.0", 0, "www.capify.org", 80)
52
56
  def local(*args)
53
57
  if args.length < 3 || args.length > 4
54
58
  raise ArgumentError, "expected 3 or 4 parameters, got #{args.length}"
@@ -69,6 +73,7 @@ module Net; module SSH; module Service
69
73
  end
70
74
  end
71
75
 
76
+ local_port = socket.addr[1] if local_port == 0 # ephemeral port was requested
72
77
  remote_host = args.shift
73
78
  remote_port = args.shift.to_i
74
79
 
@@ -89,6 +94,8 @@ module Net; module SSH; module Service
89
94
  channel[:socket].close
90
95
  end
91
96
  end
97
+
98
+ local_port
92
99
  end
93
100
 
94
101
  # Terminates an active local forwarded port. If no such forwarded port
@@ -120,15 +127,21 @@ module Net; module SSH; module Service
120
127
  # forwarded immediately. If the remote server is not able to begin the
121
128
  # listener for this request, an exception will be raised asynchronously.
122
129
  #
123
- # If you want to know when the connection is active, it will show up in the
124
- # #active_remotes list. If you want to block until the port is active, you
125
- # could do something like this:
130
+ # To request an ephemeral port on the remote server, provide 0 (zero) for
131
+ # the port number. The assigned port will show up in the # #active_remotes
132
+ # list.
126
133
  #
127
- # ssh.forward.remote(80, "www.google.com", 1234, "0.0.0.0")
128
- # ssh.loop { !ssh.forward.active_remotes.include?([1234, "0.0.0.0"]) }
134
+ # If you want to block until the port is active, you could do something
135
+ # like this:
136
+ #
137
+ # old_active_remotes = ssh.forward.active_remotes
138
+ # ssh.forward.remote(80, "www.google.com", 0, "0.0.0.0")
139
+ # ssh.loop { !(ssh.forward.active_remotes.length > old_active_remotes.length) }
140
+ # assigned_port = (ssh.forward.active_remotes - old_active_remotes).first[0]
129
141
  def remote(port, host, remote_port, remote_host="127.0.0.1")
130
142
  session.send_global_request("tcpip-forward", :string, remote_host, :long, remote_port) do |success, response|
131
143
  if success
144
+ remote_port = response.read_long if remote_port == 0
132
145
  debug { "remote forward from remote #{remote_host}:#{remote_port} to #{host}:#{port} established" }
133
146
  @remote_forwarded_ports[[remote_port, remote_host]] = Remote.new(host, port)
134
147
  else
@@ -257,6 +270,24 @@ module Net; module SSH; module Service
257
270
  end
258
271
  end
259
272
 
273
+ # not a real socket, so use a simpler behaviour
274
+ def prepare_simple_client(client, channel, type)
275
+ channel[:socket] = client
276
+
277
+ channel.on_data do |ch, data|
278
+ ch.debug { "data:#{data.length} on #{type} forwarded channel" }
279
+ ch[:socket].send(data)
280
+ end
281
+
282
+ channel.on_process do |ch|
283
+ data = ch[:socket].read(8192)
284
+ if data
285
+ ch.debug { "read #{data.length} bytes from client, sending over #{type} forwarded connection" }
286
+ ch.send_data(data)
287
+ end
288
+ end
289
+ end
290
+
260
291
  # The callback used when a new "forwarded-tcpip" channel is requested
261
292
  # by the server. This will open a new socket to the host/port specified
262
293
  # when the forwarded connection was first requested.
@@ -287,7 +318,11 @@ module Net; module SSH; module Service
287
318
 
288
319
  begin
289
320
  agent = Authentication::Agent.connect(logger)
290
- prepare_client(agent.socket, channel, :agent)
321
+ if (agent.socket.is_a? ::IO)
322
+ prepare_client(agent.socket, channel, :agent)
323
+ else
324
+ prepare_simple_client(agent.socket, channel, :agent)
325
+ end
291
326
  rescue Exception => e
292
327
  error { "attempted to connect to agent but failed: #{e.class.name} (#{e.message})" }
293
328
  raise Net::SSH::ChannelOpenFailed.new(2, "could not connect to authentication agent")
@@ -64,7 +64,13 @@ module Net; module SSH; module Transport
64
64
 
65
65
  debug { "establishing connection to #{@host}:#{@port}" }
66
66
  factory = options[:proxy] || TCPSocket
67
- @socket = timeout(options[:timeout] || 0) { @bind_address.nil? || options[:proxy] ? factory.open(@host, @port) : factory.open(@host,@port,@bind_address) }
67
+ @socket = timeout(options[:timeout] || 0) {
68
+ case
69
+ when options[:proxy] then factory.open(@host, @port, options)
70
+ when @bind_address.nil? then factory.open(@host, @port)
71
+ else factory.open(@host, @port, @bind_address)
72
+ end
73
+ }
68
74
  @socket.extend(PacketStream)
69
75
  @socket.logger = @logger
70
76
 
@@ -48,7 +48,7 @@ module Net; module SSH
48
48
  MAJOR = 2
49
49
 
50
50
  # The minor component of this version of the Net::SSH library
51
- MINOR = 7
51
+ MINOR = 8
52
52
 
53
53
  # The tiny component of this version of the Net::SSH library
54
54
  TINY = 0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "net-ssh"
8
- s.version = "2.7.0"
8
+ s.version = "2.8.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jamis Buck", "Delano Mandelbaum"]
12
- s.date = "2013-09-11"
12
+ s.date = "2014-02-01"
13
13
  s.description = "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."
14
14
  s.email = "net-ssh@solutious.com"
15
15
  s.extra_rdoc_files = [
@@ -120,6 +120,9 @@ Gem::Specification.new do |s|
120
120
  "test/authentication/test_key_manager.rb",
121
121
  "test/authentication/test_session.rb",
122
122
  "test/common.rb",
123
+ "test/configs/auth_off",
124
+ "test/configs/auth_on",
125
+ "test/configs/empty",
123
126
  "test/configs/eqsign",
124
127
  "test/configs/exact_match",
125
128
  "test/configs/host_plus",
@@ -133,6 +136,7 @@ Gem::Specification.new do |s|
133
136
  "test/connection/test_session.rb",
134
137
  "test/known_hosts/github",
135
138
  "test/manual/test_forward.rb",
139
+ "test/start/test_connection.rb",
136
140
  "test/start/test_options.rb",
137
141
  "test/start/test_transport.rb",
138
142
  "test/test_all.rb",
@@ -171,7 +175,7 @@ Gem::Specification.new do |s|
171
175
  s.licenses = ["MIT"]
172
176
  s.require_paths = ["lib"]
173
177
  s.rubyforge_project = "net-ssh"
174
- s.rubygems_version = "1.8.25"
178
+ s.rubygems_version = "1.8.23"
175
179
  s.summary = "Net::SSH: a pure-Ruby implementation of the SSH2 client protocol."
176
180
 
177
181
  if s.respond_to? :specification_version then
@@ -43,7 +43,7 @@ module Authentication
43
43
  assert_equal Net::SSH::Transport::ServerVersion::PROTO_VERSION, buffer.read_string
44
44
  s.return(SSH2_AGENT_VERSION_RESPONSE)
45
45
  end
46
- assert_raises(NotImplementedError) { agent.negotiate! }
46
+ assert_raises(Net::SSH::Authentication::AgentNotAvailable) { agent.negotiate! }
47
47
  end
48
48
 
49
49
  def test_negotiate_should_raise_error_if_response_was_unexpected
@@ -51,7 +51,7 @@ module Authentication
51
51
  assert_equal SSH2_AGENT_REQUEST_VERSION, type
52
52
  s.return(255)
53
53
  end
54
- assert_raises(Net::SSH::Authentication::AgentError) { agent.negotiate! }
54
+ assert_raises(Net::SSH::Authentication::AgentNotAvailable) { agent.negotiate! }
55
55
  end
56
56
 
57
57
  def test_negotiate_should_be_successful_with_expected_response
@@ -65,7 +65,7 @@ module Authentication
65
65
  def test_identities_should_fail_if_SSH_AGENT_FAILURE_recieved
66
66
  socket.expect do |s, type, buffer|
67
67
  assert_equal SSH2_AGENT_REQUEST_IDENTITIES, type
68
- s.return(SSH_AGENT_FAILURE)
68
+ s.return(SSH_AGENT_FAILURE)
69
69
  end
70
70
  assert_raises(Net::SSH::Authentication::AgentError) { agent.identities }
71
71
  end
@@ -73,7 +73,7 @@ module Authentication
73
73
  def test_identities_should_fail_if_SSH2_AGENT_FAILURE_recieved
74
74
  socket.expect do |s, type, buffer|
75
75
  assert_equal SSH2_AGENT_REQUEST_IDENTITIES, type
76
- s.return(SSH2_AGENT_FAILURE)
76
+ s.return(SSH2_AGENT_FAILURE)
77
77
  end
78
78
  assert_raises(Net::SSH::Authentication::AgentError) { agent.identities }
79
79
  end
@@ -81,7 +81,7 @@ module Authentication
81
81
  def test_identities_should_fail_if_SSH_COM_AGENT2_FAILURE_recieved
82
82
  socket.expect do |s, type, buffer|
83
83
  assert_equal SSH2_AGENT_REQUEST_IDENTITIES, type
84
- s.return(SSH_COM_AGENT2_FAILURE)
84
+ s.return(SSH_COM_AGENT2_FAILURE)
85
85
  end
86
86
  assert_raises(Net::SSH::Authentication::AgentError) { agent.identities }
87
87
  end
@@ -202,4 +202,4 @@ module Authentication
202
202
 
203
203
  end
204
204
 
205
- end
205
+ end
@@ -18,7 +18,7 @@ module Authentication
18
18
  manager.add "/second"
19
19
  manager.add "/third"
20
20
  manager.add "/second"
21
- assert_true manager.key_files.length == 3
21
+ assert_equal 3, manager.key_files.length
22
22
  final_files = manager.key_files.map {|item| item.split('/').last}
23
23
  assert_equal %w(first second third), final_files
24
24
  end
@@ -30,12 +30,16 @@ module Authentication
30
30
  assert !manager.use_agent?
31
31
  end
32
32
 
33
+ def test_use_agent_is_false_if_keys_only
34
+ assert !manager(:keys_only => true).use_agent?
35
+ end
36
+
33
37
  def test_each_identity_should_load_from_key_files
34
38
  manager.stubs(:agent).returns(nil)
35
39
  first = File.expand_path("/first")
36
40
  second = File.expand_path("/second")
37
41
  stub_file_private_key first, rsa
38
- stub_file_private_key second, dsa
42
+ stub_file_private_key second, dsa
39
43
 
40
44
  identities = []
41
45
  manager.each_identity { |identity| identities << identity }
@@ -43,7 +47,7 @@ module Authentication
43
47
  assert_equal 2, identities.length
44
48
  assert_equal rsa.to_blob, identities.first.to_blob
45
49
  assert_equal dsa.to_blob, identities.last.to_blob
46
-
50
+
47
51
  assert_equal({:from => :file, :file => first, :key => rsa}, manager.known_identities[rsa])
48
52
  assert_equal({:from => :file, :file => second, :key => dsa}, manager.known_identities[dsa])
49
53
  end
@@ -8,7 +8,7 @@ module Authentication
8
8
  include Net::SSH::Authentication::Constants
9
9
 
10
10
  def test_constructor_should_set_defaults
11
- assert_equal %w(none publickey hostbased password keyboard-interactive), session.auth_methods
11
+ assert_equal %w(none publickey password keyboard-interactive), session.auth_methods
12
12
  assert_equal session.auth_methods, session.allowed_auth_methods
13
13
  end
14
14
 
@@ -20,7 +20,7 @@ module Authentication
20
20
  end
21
21
 
22
22
  Net::SSH::Authentication::Methods::Publickey.any_instance.expects(:authenticate).with("next service", "username", "password").raises(Net::SSH::Authentication::DisallowedMethod)
23
- Net::SSH::Authentication::Methods::Hostbased.any_instance.expects(:authenticate).with("next service", "username", "password").returns(true)
23
+ Net::SSH::Authentication::Methods::Password.any_instance.expects(:authenticate).with("next service", "username", "password").returns(true)
24
24
  Net::SSH::Authentication::Methods::None.any_instance.expects(:authenticate).with("next service", "username", "password").returns(false)
25
25
 
26
26
  assert session.authenticate("next service", "username", "password")
@@ -44,7 +44,6 @@ module Authentication
44
44
  end
45
45
 
46
46
  Net::SSH::Authentication::Methods::Publickey.any_instance.expects(:authenticate).with("next service", "username", "password").returns(false)
47
- Net::SSH::Authentication::Methods::Hostbased.any_instance.expects(:authenticate).with("next service", "username", "password").returns(false)
48
47
  Net::SSH::Authentication::Methods::Password.any_instance.expects(:authenticate).with("next service", "username", "password").returns(false)
49
48
  Net::SSH::Authentication::Methods::KeyboardInteractive.any_instance.expects(:authenticate).with("next service", "username", "password").returns(false)
50
49
  Net::SSH::Authentication::Methods::None.any_instance.expects(:authenticate).with("next service", "username", "password").returns(false)
@@ -0,0 +1,4 @@
1
+ HostBasedAuthentication no
2
+ PasswordAuthentication no
3
+ PubKeyAuthentication no
4
+ ChallengeResponseAuthentication no
@@ -0,0 +1,4 @@
1
+ HostBasedAuthentication yes
2
+ PasswordAuthentication yes
3
+ PubKeyAuthentication yes
4
+ ChallengeResponseAuthentication yes
File without changes
@@ -1,4 +1,4 @@
1
- # $ ruby -Ilib -Itest -rrubygems test/test_forward.rb
1
+ # $ ruby -Ilib -Itest -rrubygems test/manual/test_forward.rb
2
2
 
3
3
  # Tests for the following patch:
4
4
  #
@@ -30,14 +30,6 @@ class TestForward < Test::Unit::TestCase
30
30
  [localhost ,ENV['USER'], {:keys => "~/.ssh/id_rsa", :verbose => :debug}]
31
31
  end
32
32
 
33
- def find_free_port
34
- server = TCPServer.open(0)
35
- server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR,true)
36
- port = server.addr[1]
37
- server.close
38
- port
39
- end
40
-
41
33
  def start_server_sending_lot_of_data(exceptions)
42
34
  server = TCPServer.open(0)
43
35
  Thread.start do
@@ -77,12 +69,34 @@ class TestForward < Test::Unit::TestCase
77
69
  return server
78
70
  end
79
71
 
72
+ def test_local_ephemeral_port_should_work_correctly
73
+ session = Net::SSH.start(*ssh_start_params)
74
+
75
+ assert_nothing_raised do
76
+ assigned_port = session.forward.local(0, localhost, 22)
77
+ assert_not_nil assigned_port
78
+ assert_operator assigned_port, :>, 0
79
+ end
80
+ end
81
+
82
+ def test_remote_ephemeral_port_should_work_correctly
83
+ session = Net::SSH.start(*ssh_start_params)
84
+
85
+ assert_nothing_raised do
86
+ session.forward.remote(22, localhost, 0, localhost)
87
+ session.loop { !(session.forward.active_remotes.length > 0) }
88
+ assigned_port = session.forward.active_remotes.first[0]
89
+ assert_not_nil assigned_port
90
+ assert_operator assigned_port, :>, 0
91
+ end
92
+ end
93
+
80
94
  def test_loop_should_not_abort_when_local_side_of_forward_is_closed
81
95
  session = Net::SSH.start(*ssh_start_params)
82
96
  server_exc = Queue.new
83
97
  server = start_server_sending_lot_of_data(server_exc)
84
98
  remote_port = server.addr[1]
85
- local_port = find_free_port
99
+ local_port = 0 # request ephemeral port
86
100
  session.forward.local(local_port, localhost, remote_port)
87
101
  client_done = Queue.new
88
102
  Thread.start do
@@ -104,7 +118,7 @@ class TestForward < Test::Unit::TestCase
104
118
  server_exc = Queue.new
105
119
  server = start_server_sending_lot_of_data(server_exc)
106
120
  remote_port = server.addr[1]
107
- local_port = find_free_port
121
+ local_port = 0 # request ephemeral port
108
122
  session.forward.local(local_port, localhost, remote_port)
109
123
  client_done = Queue.new
110
124
  Thread.start do
@@ -163,7 +177,7 @@ class TestForward < Test::Unit::TestCase
163
177
  session = Net::SSH.start(*ssh_start_params)
164
178
  server = start_server_closing_soon
165
179
  remote_port = server.addr[1]
166
- local_port = find_free_port
180
+ local_port = 0 # request ephemeral port
167
181
  session.forward.local(local_port, localhost, remote_port)
168
182
  client_done = Queue.new
169
183
  Thread.start do
@@ -203,8 +217,7 @@ class TestForward < Test::Unit::TestCase
203
217
  client_exception = Queue.new
204
218
  client_data = Queue.new
205
219
  remote_port = server.addr[1]
206
- local_port = find_free_port
207
- session.forward.local(local_port, localhost, remote_port)
220
+ local_port = session.forward.local(0, localhost, remote_port)
208
221
  Thread.start do
209
222
  begin
210
223
  client = TCPSocket.new(localhost, local_port)
@@ -0,0 +1,53 @@
1
+ require 'common'
2
+ require 'net/ssh'
3
+
4
+ module NetSSH
5
+ class TestConnection < Test::Unit::TestCase
6
+ attr_reader :connection_session
7
+
8
+ def setup
9
+ authentication_session = mock('authentication_session')
10
+ authentication_session.stubs(:authenticate).returns(true)
11
+ Net::SSH::Authentication::Session.stubs(:new).returns(authentication_session)
12
+ Net::SSH::Transport::Session.stubs(:new).returns(mock('transport_session'))
13
+ @connection_session = mock('connection_session')
14
+ Net::SSH::Connection::Session.expects(:new => connection_session)
15
+ end
16
+
17
+ def test_close_connection_on_exception
18
+ @connection_session.expects(:closed?).returns(false)
19
+ @connection_session.expects(:close).once
20
+
21
+ begin
22
+ Net::SSH.start('localhost', 'testuser') { raise "error" }
23
+ rescue RuntimeError
24
+ # We aren't interested in the exception
25
+ end
26
+ end
27
+
28
+ def test_close_connection_on_exception_only_if_still_open
29
+ conn_open = states('conn').starts_as(true)
30
+ @connection_session.expects(:close).then(conn_open.is(false)).once
31
+ @connection_session.expects(:closed?).when(conn_open.is(false)).returns(true)
32
+
33
+ begin
34
+ Net::SSH.start('localhost', 'testuser') do |ssh|
35
+ ssh.close
36
+ raise "error"
37
+ end
38
+ rescue RuntimeError
39
+ # We aren't interested in the exception
40
+ end
41
+ end
42
+
43
+ def test_return_value_is_returned
44
+ @connection_session.expects(:closed?).returns(false)
45
+ @connection_session.expects(:close).once
46
+
47
+ val = 1
48
+ retval = Net::SSH.start('localhost', 'testuser') { val }
49
+ assert_equal(val, retval)
50
+ end
51
+ end
52
+ end
53
+
@@ -97,7 +97,7 @@ class TestConfig < Test::Unit::TestCase
97
97
  assert_equal 6, net_ssh[:compression_level]
98
98
  assert_equal 100, net_ssh[:timeout]
99
99
  assert_equal true, net_ssh[:forward_agent]
100
- assert_equal %w(hostbased password publickey), net_ssh[:auth_methods].sort
100
+ assert_equal %w(hostbased keyboard-interactive none password publickey), net_ssh[:auth_methods].sort
101
101
  assert_equal %w(d e f), net_ssh[:host_key]
102
102
  assert_equal %w(g h i), net_ssh[:keys]
103
103
  assert_equal %w(j k l), net_ssh[:hmac]
@@ -106,6 +106,42 @@ class TestConfig < Test::Unit::TestCase
106
106
  assert_equal "127.0.0.1", net_ssh[:bind_address]
107
107
  assert_equal [/^LC_.*$/], net_ssh[:send_env]
108
108
  end
109
+
110
+ def test_translate_should_turn_off_authentication_methods
111
+ open_ssh = {
112
+ 'hostbasedauthentication' => false,
113
+ 'passwordauthentication' => false,
114
+ 'pubkeyauthentication' => false,
115
+ 'challengeresponseauthentication' => false
116
+ }
117
+
118
+ net_ssh = Net::SSH::Config.translate(open_ssh)
119
+
120
+ assert_equal %w(none), net_ssh[:auth_methods].sort
121
+ end
122
+
123
+ def test_translate_should_turn_on_authentication_methods
124
+ open_ssh = {
125
+ 'hostbasedauthentication' => true,
126
+ 'passwordauthentication' => true,
127
+ 'pubkeyauthentication' => true,
128
+ 'challengeresponseauthentication' => true
129
+ }
130
+
131
+ net_ssh = Net::SSH::Config.translate(open_ssh)
132
+
133
+ assert_equal %w(hostbased keyboard-interactive none password publickey), net_ssh[:auth_methods].sort
134
+ end
135
+
136
+ def test_for_should_turn_off_authentication_methods
137
+ config = Net::SSH::Config.for("test.host", [config(:empty), config(:auth_off), config(:auth_on)])
138
+ assert_equal %w(none), config[:auth_methods].sort
139
+ end
140
+
141
+ def test_for_should_turn_on_authentication_methods
142
+ config = Net::SSH::Config.for("test.host", [config(:empty), config(:auth_on), config(:auth_off)])
143
+ assert_equal %w(hostbased keyboard-interactive none password publickey), config[:auth_methods].sort
144
+ end
109
145
 
110
146
  def test_load_with_plus_sign_hosts
111
147
  config = Net::SSH::Config.load(config(:host_plus), "test.host")
@@ -65,6 +65,20 @@ class TestKeyFactory < Test::Unit::TestCase
65
65
  assert_equal rsa_key.to_blob, Net::SSH::KeyFactory.load_public_key(@key_file).to_blob
66
66
  end
67
67
 
68
+ def test_load_public_rsa_key_with_comment_should_return_key
69
+ File.expects(:read).with(@key_file).returns(public(rsa_key) + " key_comment")
70
+ assert_equal rsa_key.to_blob, Net::SSH::KeyFactory.load_public_key(@key_file).to_blob
71
+ end
72
+
73
+ def test_load_public_rsa_key_with_options_should_return_key
74
+ File.expects(:read).with(@key_file).returns(public(rsa_key, 'environment="FOO=bar"'))
75
+ assert_equal rsa_key.to_blob, Net::SSH::KeyFactory.load_public_key(@key_file).to_blob
76
+ end
77
+
78
+ def test_load_public_rsa_key_with_options_and_comment_should_return_key
79
+ File.expects(:read).with(@key_file).returns(public(rsa_key, 'environment="FOO=bar"') + " key_comment")
80
+ assert_equal rsa_key.to_blob, Net::SSH::KeyFactory.load_public_key(@key_file).to_blob
81
+ end
68
82
  if defined?(OpenSSL::PKey::EC)
69
83
  def test_load_unencrypted_private_ecdsa_sha2_nistp256_key_should_return_key
70
84
  File.expects(:read).with("/key-file").returns(ecdsa_sha2_nistp256_key.to_pem)
@@ -165,8 +179,12 @@ WEKt5v3QsUEgVrzkM4K9UbI=
165
179
  key.export(OpenSSL::Cipher::Cipher.new("des-ede3-cbc"), password)
166
180
  end
167
181
 
168
- def public(key)
169
- result = "#{key.ssh_type} "
182
+ def public(key, args = nil)
183
+ result = ""
184
+ if !args.nil?
185
+ result << "#{args} "
186
+ end
187
+ result << "#{key.ssh_type} "
170
188
  result << [Net::SSH::Buffer.from(:key, key).to_s].pack("m*").strip.tr("\n\r\t ", "")
171
189
  result << " joe@host.test"
172
190
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-ssh
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.0
4
+ version: 2.8.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-09-11 00:00:00.000000000 Z
13
+ date: 2014-02-01 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: test-unit
@@ -157,6 +157,9 @@ files:
157
157
  - test/authentication/test_key_manager.rb
158
158
  - test/authentication/test_session.rb
159
159
  - test/common.rb
160
+ - test/configs/auth_off
161
+ - test/configs/auth_on
162
+ - test/configs/empty
160
163
  - test/configs/eqsign
161
164
  - test/configs/exact_match
162
165
  - test/configs/host_plus
@@ -170,6 +173,7 @@ files:
170
173
  - test/connection/test_session.rb
171
174
  - test/known_hosts/github
172
175
  - test/manual/test_forward.rb
176
+ - test/start/test_connection.rb
173
177
  - test/start/test_options.rb
174
178
  - test/start/test_transport.rb
175
179
  - test/test_all.rb
@@ -224,7 +228,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
224
228
  version: '0'
225
229
  requirements: []
226
230
  rubyforge_project: net-ssh
227
- rubygems_version: 1.8.25
231
+ rubygems_version: 1.8.23
228
232
  signing_key:
229
233
  specification_version: 3
230
234
  summary: ! 'Net::SSH: a pure-Ruby implementation of the SSH2 client protocol.'