net-ssh 2.7.0 → 2.8.0

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