net-ssh 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc CHANGED
@@ -1,4 +1,8 @@
1
1
 
2
+ === 2.4.0 / 17 May 2012
3
+
4
+ * Support for JRuby + Pageant + Windows [arturaz]
5
+
2
6
  === 2.3.0 / 11 Jan 2012
3
7
 
4
8
  * Support for hmac-sha2 and diffie-hellman-group-exchange-sha256 [Ryosuke Yamazaki]
@@ -1,179 +1,23 @@
1
1
  require 'net/ssh/buffer'
2
2
  require 'net/ssh/errors'
3
3
  require 'net/ssh/loggable'
4
- require 'net/ssh/transport/server_version'
5
-
6
- # Only load pageant on Windows, Ruby 1.8.x
7
- if File::ALT_SEPARATOR && !(RUBY_PLATFORM =~ /java/) && RUBY_VERSION < "1.9"
8
- require 'net/ssh/authentication/pageant'
9
- end
10
4
 
11
5
  module Net; module SSH; module Authentication
6
+ PLATFORM = File::ALT_SEPARATOR \
7
+ ? RUBY_PLATFORM =~ /java/ ? :java_win32 : :win32 \
8
+ : RUBY_PLATFORM =~ /java/ ? :java : :unix
12
9
 
13
10
  # A trivial exception class for representing agent-specific errors.
14
11
  class AgentError < Net::SSH::Exception; end
15
12
 
16
13
  # An exception for indicating that the SSH agent is not available.
17
14
  class AgentNotAvailable < AgentError; end
18
-
19
- # This class implements a simple client for the ssh-agent protocol. It
20
- # does not implement any specific protocol, but instead copies the
21
- # behavior of the ssh-agent functions in the OpenSSH library (3.8).
22
- #
23
- # This means that although it behaves like a SSH1 client, it also has
24
- # some SSH2 functionality (like signing data).
25
- class Agent
26
- include Loggable
27
-
28
- # A simple module for extending keys, to allow comments to be specified
29
- # for them.
30
- module Comment
31
- attr_accessor :comment
32
- end
33
-
34
- SSH2_AGENT_REQUEST_VERSION = 1
35
- SSH2_AGENT_REQUEST_IDENTITIES = 11
36
- SSH2_AGENT_IDENTITIES_ANSWER = 12
37
- SSH2_AGENT_SIGN_REQUEST = 13
38
- SSH2_AGENT_SIGN_RESPONSE = 14
39
- SSH2_AGENT_FAILURE = 30
40
- SSH2_AGENT_VERSION_RESPONSE = 103
41
-
42
- SSH_COM_AGENT2_FAILURE = 102
43
-
44
- SSH_AGENT_REQUEST_RSA_IDENTITIES = 1
45
- SSH_AGENT_RSA_IDENTITIES_ANSWER1 = 2
46
- SSH_AGENT_RSA_IDENTITIES_ANSWER2 = 5
47
- SSH_AGENT_FAILURE = 5
48
-
49
- # The underlying socket being used to communicate with the SSH agent.
50
- attr_reader :socket
51
-
52
- # Instantiates a new agent object, connects to a running SSH agent,
53
- # negotiates the agent protocol version, and returns the agent object.
54
- def self.connect(logger=nil)
55
- agent = new(logger)
56
- agent.connect!
57
- agent.negotiate!
58
- agent
59
- end
60
-
61
- # Creates a new Agent object, using the optional logger instance to
62
- # report status.
63
- def initialize(logger=nil)
64
- self.logger = logger
65
- end
66
-
67
- # Connect to the agent process using the socket factory and socket name
68
- # given by the attribute writers. If the agent on the other end of the
69
- # socket reports that it is an SSH2-compatible agent, this will fail
70
- # (it only supports the ssh-agent distributed by OpenSSH).
71
- def connect!
72
- begin
73
- debug { "connecting to ssh-agent" }
74
- @socket = agent_socket_factory.open(ENV['SSH_AUTH_SOCK'])
75
- rescue
76
- error { "could not connect to ssh-agent" }
77
- raise AgentNotAvailable, $!.message
78
- end
79
- end
80
-
81
- # Attempts to negotiate the SSH agent protocol version. Raises an error
82
- # if the version could not be negotiated successfully.
83
- def negotiate!
84
- # determine what type of agent we're communicating with
85
- type, body = send_and_wait(SSH2_AGENT_REQUEST_VERSION, :string, Transport::ServerVersion::PROTO_VERSION)
86
-
87
- if type == SSH2_AGENT_VERSION_RESPONSE
88
- raise NotImplementedError, "SSH2 agents are not yet supported"
89
- elsif type != SSH_AGENT_RSA_IDENTITIES_ANSWER1 && type != SSH_AGENT_RSA_IDENTITIES_ANSWER2
90
- raise AgentError, "unknown response from agent: #{type}, #{body.to_s.inspect}"
91
- end
92
- end
93
-
94
- # Return an array of all identities (public keys) known to the agent.
95
- # Each key returned is augmented with a +comment+ property which is set
96
- # to the comment returned by the agent for that key.
97
- def identities
98
- type, body = send_and_wait(SSH2_AGENT_REQUEST_IDENTITIES)
99
- raise AgentError, "could not get identity count" if agent_failed(type)
100
- raise AgentError, "bad authentication reply: #{type}" if type != SSH2_AGENT_IDENTITIES_ANSWER
101
-
102
- identities = []
103
- body.read_long.times do
104
- key = Buffer.new(body.read_string).read_key
105
- key.extend(Comment)
106
- key.comment = body.read_string
107
- identities.push key
108
- end
109
-
110
- return identities
111
- end
112
-
113
- # Closes this socket. This agent reference is no longer able to
114
- # query the agent.
115
- def close
116
- @socket.close
117
- end
118
-
119
- # Using the agent and the given public key, sign the given data. The
120
- # signature is returned in SSH2 format.
121
- def sign(key, data)
122
- type, reply = send_and_wait(SSH2_AGENT_SIGN_REQUEST, :string, Buffer.from(:key, key), :string, data, :long, 0)
123
-
124
- if agent_failed(type)
125
- raise AgentError, "agent could not sign data with requested identity"
126
- elsif type != SSH2_AGENT_SIGN_RESPONSE
127
- raise AgentError, "bad authentication response #{type}"
128
- end
129
-
130
- return reply.read_string
131
- end
132
-
133
- private
134
-
135
- # Returns the agent socket factory to use.
136
- def agent_socket_factory
137
- if File::ALT_SEPARATOR
138
- Pageant::Socket
139
- else
140
- UNIXSocket
141
- end
142
- end
143
-
144
- # Send a new packet of the given type, with the associated data.
145
- def send_packet(type, *args)
146
- buffer = Buffer.from(*args)
147
- data = [buffer.length + 1, type.to_i, buffer.to_s].pack("NCA*")
148
- debug { "sending agent request #{type} len #{buffer.length}" }
149
- @socket.send data, 0
150
- end
151
-
152
- # Read the next packet from the agent. This will return a two-part
153
- # tuple consisting of the packet type, and the packet's body (which
154
- # is returned as a Net::SSH::Buffer).
155
- def read_packet
156
- buffer = Net::SSH::Buffer.new(@socket.read(4))
157
- buffer.append(@socket.read(buffer.read_long))
158
- type = buffer.read_byte
159
- debug { "received agent packet #{type} len #{buffer.length-4}" }
160
- return type, buffer
161
- end
162
-
163
- # Send the given packet and return the subsequent reply from the agent.
164
- # (See #send_packet and #read_packet).
165
- def send_and_wait(type, *args)
166
- send_packet(type, *args)
167
- read_packet
168
- end
169
-
170
- # Returns +true+ if the parameter indicates a "failure" response from
171
- # the agent, and +false+ otherwise.
172
- def agent_failed(type)
173
- type == SSH_AGENT_FAILURE ||
174
- type == SSH2_AGENT_FAILURE ||
175
- type == SSH_COM_AGENT2_FAILURE
176
- end
177
- end
178
-
179
15
  end; end; end
16
+
17
+ case Net::SSH::Authentication::PLATFORM
18
+ when :java_win32
19
+ # Java pageant requires whole different agent.
20
+ require 'net/ssh/authentication/agent/java_pageant'
21
+ else
22
+ require 'net/ssh/authentication/agent/socket'
23
+ end
@@ -0,0 +1,85 @@
1
+ require 'jruby_pageant'
2
+
3
+ module Net; module SSH; module Authentication
4
+
5
+ # This class implements an agent for JRuby + Pageant.
6
+ #
7
+ # Written by Artūras Šlajus <arturas.slajus@gmail.com>
8
+ class Agent
9
+ include Loggable
10
+ include JRubyPageant
11
+
12
+ # A simple module for extending keys, to allow blobs and comments to be
13
+ # specified for them.
14
+ module Key
15
+ # :blob is used by OpenSSL::PKey::RSA#to_blob
16
+ attr_accessor :java_blob
17
+ attr_accessor :comment
18
+ end
19
+
20
+ # Instantiates a new agent object, connects to a running SSH agent,
21
+ # negotiates the agent protocol version, and returns the agent object.
22
+ def self.connect(logger=nil)
23
+ agent = new(logger)
24
+ agent.connect!
25
+ agent
26
+ end
27
+
28
+ # Creates a new Agent object, using the optional logger instance to
29
+ # report status.
30
+ def initialize(logger=nil)
31
+ self.logger = logger
32
+ end
33
+
34
+ # Connect to the agent process using the socket factory and socket name
35
+ # given by the attribute writers. If the agent on the other end of the
36
+ # socket reports that it is an SSH2-compatible agent, this will fail
37
+ # (it only supports the ssh-agent distributed by OpenSSH).
38
+ def connect!
39
+ debug { "connecting to Pageant ssh-agent (via java connector)" }
40
+ @agent_proxy = JRubyPageant.create
41
+ unless @agent_proxy.is_running
42
+ raise AgentNotAvailable, "Pageant is not running!"
43
+ end
44
+ debug { "connection to Pageant ssh-agent (via java connector) succeeded" }
45
+ rescue AgentProxyException => e
46
+ error { "could not connect to Pageant ssh-agent (via java connector)" }
47
+ raise AgentNotAvailable, e.message, e.backtrace
48
+ end
49
+
50
+ # Return an array of all identities (public keys) known to the agent.
51
+ # Each key returned is augmented with a +comment+ property which is set
52
+ # to the comment returned by the agent for that key.
53
+ def identities
54
+ debug { "getting identities from Pageant" }
55
+ @agent_proxy.get_identities.map do |identity|
56
+ blob = identity.get_blob
57
+ key = Buffer.new(String.from_java_bytes(blob)).read_key
58
+ key.extend(Key)
59
+ key.java_blob = blob
60
+ key.comment = String.from_java_bytes(identity.get_comment)
61
+ key
62
+ end
63
+ rescue AgentProxyException => e
64
+ raise AgentError, "Cannot get identities: #{e.message}", e.backtrace
65
+ end
66
+
67
+ # Simulate agent close. This agent reference is no longer able to
68
+ # query the agent.
69
+ def close
70
+ @agent_proxy = nil
71
+ end
72
+
73
+ # Using the agent and the given public key, sign the given data. The
74
+ # signature is returned in SSH2 format.
75
+ def sign(key, data)
76
+ signed = @agent_proxy.sign(key.java_blob, data.to_java_bytes)
77
+ String.from_java_bytes(signed)
78
+ rescue AgentProxyException => e
79
+ raise AgentError,
80
+ "agent could not sign data with requested identity: #{e.message}",
81
+ e.backtrace
82
+ end
83
+ end
84
+
85
+ end; end; end
@@ -0,0 +1,170 @@
1
+ require 'net/ssh/transport/server_version'
2
+
3
+ # Only load pageant on Windows
4
+ if Net::SSH::Authentication::PLATFORM == :win32
5
+ require 'net/ssh/authentication/pageant'
6
+ end
7
+
8
+ module Net; module SSH; module Authentication
9
+
10
+ # This class implements a simple client for the ssh-agent protocol. It
11
+ # does not implement any specific protocol, but instead copies the
12
+ # behavior of the ssh-agent functions in the OpenSSH library (3.8).
13
+ #
14
+ # This means that although it behaves like a SSH1 client, it also has
15
+ # some SSH2 functionality (like signing data).
16
+ class Agent
17
+ include Loggable
18
+
19
+ # A simple module for extending keys, to allow comments to be specified
20
+ # for them.
21
+ module Comment
22
+ attr_accessor :comment
23
+ end
24
+
25
+ SSH2_AGENT_REQUEST_VERSION = 1
26
+ SSH2_AGENT_REQUEST_IDENTITIES = 11
27
+ SSH2_AGENT_IDENTITIES_ANSWER = 12
28
+ SSH2_AGENT_SIGN_REQUEST = 13
29
+ SSH2_AGENT_SIGN_RESPONSE = 14
30
+ SSH2_AGENT_FAILURE = 30
31
+ SSH2_AGENT_VERSION_RESPONSE = 103
32
+
33
+ SSH_COM_AGENT2_FAILURE = 102
34
+
35
+ SSH_AGENT_REQUEST_RSA_IDENTITIES = 1
36
+ SSH_AGENT_RSA_IDENTITIES_ANSWER1 = 2
37
+ SSH_AGENT_RSA_IDENTITIES_ANSWER2 = 5
38
+ SSH_AGENT_FAILURE = 5
39
+
40
+ # The underlying socket being used to communicate with the SSH agent.
41
+ attr_reader :socket
42
+
43
+ # Instantiates a new agent object, connects to a running SSH agent,
44
+ # negotiates the agent protocol version, and returns the agent object.
45
+ def self.connect(logger=nil)
46
+ agent = new(logger)
47
+ agent.connect!
48
+ agent.negotiate!
49
+ agent
50
+ end
51
+
52
+ # Creates a new Agent object, using the optional logger instance to
53
+ # report status.
54
+ def initialize(logger=nil)
55
+ self.logger = logger
56
+ end
57
+
58
+ # Connect to the agent process using the socket factory and socket name
59
+ # given by the attribute writers. If the agent on the other end of the
60
+ # socket reports that it is an SSH2-compatible agent, this will fail
61
+ # (it only supports the ssh-agent distributed by OpenSSH).
62
+ def connect!
63
+ begin
64
+ debug { "connecting to ssh-agent" }
65
+ @socket = agent_socket_factory.open(ENV['SSH_AUTH_SOCK'])
66
+ rescue
67
+ error { "could not connect to ssh-agent" }
68
+ raise AgentNotAvailable, $!.message
69
+ end
70
+ end
71
+
72
+ # Attempts to negotiate the SSH agent protocol version. Raises an error
73
+ # if the version could not be negotiated successfully.
74
+ def negotiate!
75
+ # determine what type of agent we're communicating with
76
+ type, body = send_and_wait(SSH2_AGENT_REQUEST_VERSION, :string, Transport::ServerVersion::PROTO_VERSION)
77
+
78
+ if type == SSH2_AGENT_VERSION_RESPONSE
79
+ raise NotImplementedError, "SSH2 agents are not yet supported"
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}"
82
+ end
83
+ end
84
+
85
+ # Return an array of all identities (public keys) known to the agent.
86
+ # Each key returned is augmented with a +comment+ property which is set
87
+ # to the comment returned by the agent for that key.
88
+ def identities
89
+ type, body = send_and_wait(SSH2_AGENT_REQUEST_IDENTITIES)
90
+ raise AgentError, "could not get identity count" if agent_failed(type)
91
+ raise AgentError, "bad authentication reply: #{type}" if type != SSH2_AGENT_IDENTITIES_ANSWER
92
+
93
+ identities = []
94
+ body.read_long.times do
95
+ key = Buffer.new(body.read_string).read_key
96
+ key.extend(Comment)
97
+ key.comment = body.read_string
98
+ identities.push key
99
+ end
100
+
101
+ return identities
102
+ end
103
+
104
+ # Closes this socket. This agent reference is no longer able to
105
+ # query the agent.
106
+ def close
107
+ @socket.close
108
+ end
109
+
110
+ # Using the agent and the given public key, sign the given data. The
111
+ # signature is returned in SSH2 format.
112
+ def sign(key, data)
113
+ type, reply = send_and_wait(SSH2_AGENT_SIGN_REQUEST, :string, Buffer.from(:key, key), :string, data, :long, 0)
114
+
115
+ if agent_failed(type)
116
+ raise AgentError, "agent could not sign data with requested identity"
117
+ elsif type != SSH2_AGENT_SIGN_RESPONSE
118
+ raise AgentError, "bad authentication response #{type}"
119
+ end
120
+
121
+ return reply.read_string
122
+ end
123
+
124
+ private
125
+
126
+ # Returns the agent socket factory to use.
127
+ def agent_socket_factory
128
+ if Net::SSH::Authentication::PLATFORM == :win32
129
+ Pageant::socket_factory
130
+ else
131
+ UNIXSocket
132
+ end
133
+ end
134
+
135
+ # Send a new packet of the given type, with the associated data.
136
+ def send_packet(type, *args)
137
+ buffer = Buffer.from(*args)
138
+ data = [buffer.length + 1, type.to_i, buffer.to_s].pack("NCA*")
139
+ debug { "sending agent request #{type} len #{buffer.length}" }
140
+ @socket.send data, 0
141
+ end
142
+
143
+ # Read the next packet from the agent. This will return a two-part
144
+ # tuple consisting of the packet type, and the packet's body (which
145
+ # is returned as a Net::SSH::Buffer).
146
+ def read_packet
147
+ buffer = Net::SSH::Buffer.new(@socket.read(4))
148
+ buffer.append(@socket.read(buffer.read_long))
149
+ type = buffer.read_byte
150
+ debug { "received agent packet #{type} len #{buffer.length-4}" }
151
+ return type, buffer
152
+ end
153
+
154
+ # Send the given packet and return the subsequent reply from the agent.
155
+ # (See #send_packet and #read_packet).
156
+ def send_and_wait(type, *args)
157
+ send_packet(type, *args)
158
+ read_packet
159
+ end
160
+
161
+ # Returns +true+ if the parameter indicates a "failure" response from
162
+ # the agent, and +false+ otherwise.
163
+ def agent_failed(type)
164
+ type == SSH_AGENT_FAILURE ||
165
+ type == SSH2_AGENT_FAILURE ||
166
+ type == SSH_COM_AGENT2_FAILURE
167
+ end
168
+ end
169
+
170
+ end; end; end
@@ -1,5 +1,13 @@
1
1
  require 'dl/import'
2
- require 'dl/struct'
2
+
3
+ if RUBY_VERSION < "1.9"
4
+ require 'dl/struct'
5
+ end
6
+
7
+ if RUBY_VERSION =~ /^1.9/
8
+ require 'dl/types'
9
+ require 'dl'
10
+ end
3
11
 
4
12
  require 'net/ssh/errors'
5
13
 
@@ -17,15 +25,23 @@ module Net; module SSH; module Authentication
17
25
  # From Putty pageant.c
18
26
  AGENT_MAX_MSGLEN = 8192
19
27
  AGENT_COPYDATA_ID = 0x804e50ba
20
-
28
+
21
29
  # The definition of the Windows methods and data structures used in
22
30
  # communicating with the pageant process.
23
31
  module Win
24
- extend DL::Importable
25
-
26
- dlload 'user32'
27
- dlload 'kernel32'
28
-
32
+ if RUBY_VERSION < "1.9"
33
+ extend DL::Importable
34
+
35
+ dlload 'user32'
36
+ dlload 'kernel32'
37
+ end
38
+
39
+ if RUBY_VERSION =~ /^1.9/
40
+ extend DL::Importer
41
+ dlload 'user32','kernel32'
42
+ include DL::Win32Types
43
+ end
44
+
29
45
  typealias("LPCTSTR", "char *") # From winnt.h
30
46
  typealias("LPVOID", "void *") # From winnt.h
31
47
  typealias("LPCVOID", "const void *") # From windef.h
@@ -52,7 +68,7 @@ module Net; module SSH; module Authentication
52
68
  # args: hFile, (ignored), flProtect, dwMaximumSizeHigh,
53
69
  # dwMaximumSizeLow, lpName
54
70
  extern 'HANDLE CreateFileMapping(HANDLE, void *, DWORD, DWORD, ' +
55
- 'DWORD, LPCTSTR)'
71
+ 'DWORD, LPCTSTR)'
56
72
 
57
73
  # args: hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh,
58
74
  # dwfileOffsetLow, dwNumberOfBytesToMap
@@ -66,7 +82,11 @@ module Net; module SSH; module Authentication
66
82
 
67
83
  # args: hWnd, Msg, wParam, lParam, fuFlags, uTimeout, lpdwResult
68
84
  extern 'LRESULT SendMessageTimeout(HWND, UINT, WPARAM, LPARAM, ' +
69
- 'UINT, UINT, PDWORD_PTR)'
85
+ 'UINT, UINT, PDWORD_PTR)'
86
+ if RUBY_VERSION < "1.9"
87
+ alias_method :FindWindow,:findWindow
88
+ module_function :FindWindow
89
+ end
70
90
  end
71
91
 
72
92
  # This is the pseudo-socket implementation that mimics the interface of
@@ -87,7 +107,7 @@ module Net; module SSH; module Authentication
87
107
  # Create a new instance that communicates with the running pageant
88
108
  # instance. If no such instance is running, this will cause an error.
89
109
  def initialize
90
- @win = Win.findWindow("Pageant", "Pageant")
110
+ @win = Win.FindWindow("Pageant", "Pageant")
91
111
 
92
112
  if @win == 0
93
113
  raise Net::SSH::Exception,
@@ -97,7 +117,7 @@ module Net; module SSH; module Authentication
97
117
  @res = nil
98
118
  @pos = 0
99
119
  end
100
-
120
+
101
121
  # Forwards the data to #send_query, ignoring any arguments after
102
122
  # the first. Returns 0.
103
123
  def send(data, *args)
@@ -132,11 +152,11 @@ module Net; module SSH; module Authentication
132
152
  end
133
153
 
134
154
  ptr[0] = query
135
-
155
+
136
156
  cds = [AGENT_COPYDATA_ID, mapname.size + 1, mapname].
137
157
  pack("LLp").to_ptr
138
158
  succ = Win.sendMessageTimeout(@win, Win::WM_COPYDATA, Win::NULL,
139
- cds, Win::SMTO_NORMAL, 5000, id)
159
+ cds, Win::SMTO_NORMAL, 5000, id)
140
160
 
141
161
  if succ > 0
142
162
  retlen = 4 + ptr.to_s(4).unpack("N")[0]
@@ -178,6 +198,67 @@ module Net; module SSH; module Authentication
178
198
 
179
199
  end
180
200
 
201
+ # Socket changes for Ruby 1.9
202
+ # Functionality is the same as Ruby 1.8 but it includes the new calls to
203
+ # the DL module as well as other pointer transformations
204
+ class Socket19 < Socket
205
+ # Packages the given query string and sends it to the pageant
206
+ # process via the Windows messaging subsystem. The result is
207
+ # cached, to be returned piece-wise when #read is called.
208
+ def send_query(query)
209
+ res = nil
210
+ filemap = 0
211
+ ptr = nil
212
+ id = DL.malloc(DL::SIZEOF_LONG)
213
+
214
+ mapname = "PageantRequest%08x\000" % Win.GetCurrentThreadId()
215
+
216
+ filemap = Win.CreateFileMapping(Win::INVALID_HANDLE_VALUE,
217
+ Win::NULL,
218
+ Win::PAGE_READWRITE, 0,
219
+ AGENT_MAX_MSGLEN, mapname)
220
+
221
+ if filemap == 0 || filemap == Win::INVALID_HANDLE_VALUE
222
+ raise Net::SSH::Exception,
223
+ "Creation of file mapping failed"
224
+ end
225
+
226
+ ptr = Win.MapViewOfFile(filemap, Win::FILE_MAP_WRITE, 0, 0,
227
+ 0)
228
+
229
+ if ptr.nil? || ptr.null?
230
+ raise Net::SSH::Exception, "Mapping of file failed"
231
+ end
232
+
233
+ DL::CPtr.new(ptr)[0,query.size]=query
234
+
235
+ cds = DL::CPtr.to_ptr [AGENT_COPYDATA_ID, mapname.size + 1, mapname].
236
+ pack("LLp")
237
+ succ = Win.SendMessageTimeout(@win, Win::WM_COPYDATA, Win::NULL,
238
+ cds, Win::SMTO_NORMAL, 5000, id)
239
+
240
+ if succ > 0
241
+ retlen = 4 + ptr.to_s(4).unpack("N")[0]
242
+ res = ptr.to_s(retlen)
243
+ end
244
+
245
+ return res
246
+ ensure
247
+ Win.UnmapViewOfFile(ptr) unless ptr.nil? || ptr.null?
248
+ Win.CloseHandle(filemap) if filemap != 0
249
+ end
250
+ end
251
+
252
+ # Selects which socket to use depending on the ruby version
253
+ # This is needed due changes in the DL module.
254
+ def self.socket_factory
255
+ if RUBY_VERSION < "1.9"
256
+ Socket
257
+ else
258
+ Socket19
259
+ end
260
+ end
261
+
181
262
  end
182
263
 
183
264
  end; end; end
@@ -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 = 3
51
+ MINOR = 4
52
52
 
53
53
  # The tiny component of this version of the Net::SSH library
54
54
  TINY = 0
data/net-ssh.gemspec CHANGED
@@ -1,21 +1,26 @@
1
1
  @spec = Gem::Specification.new do |s|
2
- s.name = "net-ssh"
3
- s.rubyforge_project = 'net-ssh'
4
- s.version = "2.3.0"
5
- s.summary = "Net::SSH: a pure-Ruby implementation of the SSH2 client protocol."
6
- s.description = s.summary
7
- s.authors = ["Jamis Buck", "Delano Mandelbaum"]
8
- s.email = ["net-ssh@solutious.com"]
9
- s.homepage = "http://github.com/net-ssh/net-ssh"
10
-
2
+ s.name = "net-ssh"
3
+ s.rubyforge_project = 'net-ssh'
4
+ s.version = "2.4.0"
5
+ s.summary = "Net::SSH: a pure-Ruby implementation of the SSH2 client protocol."
6
+ s.description = s.summary + " It allows you to write programs that invoke and interact with processes on remote servers, via SSH2."
7
+ s.authors = ["Jamis Buck", "Delano Mandelbaum"]
8
+ s.email = ["net-ssh@solutious.com"]
9
+ s.homepage = "http://github.com/net-ssh/net-ssh"
10
+
11
11
  s.extra_rdoc_files = %w[README.rdoc THANKS.rdoc CHANGELOG.rdoc]
12
12
  s.has_rdoc = true
13
13
  s.rdoc_options = ["--line-numbers", "--title", s.summary, "--main", "README.rdoc"]
14
14
  s.require_paths = %w[lib]
15
15
  s.rubygems_version = '1.3.2'
16
-
16
+
17
+ # This has two flavours with java one actually doing something and other
18
+ # one just raising error. This is a workaround for no ability to specify
19
+ # platform specific dependencies in gemspecs.
20
+ s.add_dependency 'jruby-pageant', ">=1.0.2"
21
+
17
22
  s.executables = %w[]
18
-
23
+
19
24
  # = MANIFEST =
20
25
  s.files = %w(
21
26
  CHANGELOG.rdoc
@@ -26,6 +31,8 @@
26
31
  THANKS.rdoc
27
32
  lib/net/ssh.rb
28
33
  lib/net/ssh/authentication/agent.rb
34
+ lib/net/ssh/authentication/agent/java_pageant.rb
35
+ lib/net/ssh/authentication/agent/socket.rb
29
36
  lib/net/ssh/authentication/constants.rb
30
37
  lib/net/ssh/authentication/key_manager.rb
31
38
  lib/net/ssh/authentication/methods/abstract.rb
@@ -141,5 +148,5 @@
141
148
  test/transport/test_state.rb
142
149
  )
143
150
 
144
-
151
+
145
152
  end
@@ -18,7 +18,9 @@ module Authentication
18
18
  manager.add "/second"
19
19
  manager.add "/third"
20
20
  manager.add "/second"
21
- assert_equal %w(/first /second /third), manager.key_files
21
+ assert_true manager.key_files.length == 3
22
+ final_files = manager.key_files.map {|item| item.split('/').last}
23
+ assert_equal %w(first second third), final_files
22
24
  end
23
25
 
24
26
  def test_use_agent_should_be_set_to_false_if_agent_could_not_be_found
@@ -30,9 +32,10 @@ module Authentication
30
32
 
31
33
  def test_each_identity_should_load_from_key_files
32
34
  manager.stubs(:agent).returns(nil)
33
-
34
- stub_file_private_key "/first", rsa
35
- stub_file_private_key "/second", dsa
35
+ first = File.expand_path("/first")
36
+ second = File.expand_path("/second")
37
+ stub_file_private_key first, rsa
38
+ stub_file_private_key second, dsa
36
39
 
37
40
  identities = []
38
41
  manager.each_identity { |identity| identities << identity }
@@ -40,9 +43,9 @@ module Authentication
40
43
  assert_equal 2, identities.length
41
44
  assert_equal rsa.to_blob, identities.first.to_blob
42
45
  assert_equal dsa.to_blob, identities.last.to_blob
43
-
44
- assert_equal({:from => :file, :file => "/first", :key => rsa}, manager.known_identities[rsa])
45
- assert_equal({:from => :file, :file => "/second", :key => dsa}, manager.known_identities[dsa])
46
+
47
+ assert_equal({:from => :file, :file => first, :key => rsa}, manager.known_identities[rsa])
48
+ assert_equal({:from => :file, :file => second, :key => dsa}, manager.known_identities[dsa])
46
49
  end
47
50
 
48
51
  def test_identities_should_load_from_agent
@@ -62,7 +65,8 @@ module Authentication
62
65
  def test_only_identities_with_key_files_should_load_from_agent_of_keys_only_set
63
66
  manager(:keys_only => true).stubs(:agent).returns(agent)
64
67
 
65
- stub_file_private_key "/first", rsa
68
+ first = File.expand_path("/first")
69
+ stub_file_private_key first, rsa
66
70
 
67
71
  identities = []
68
72
  manager.each_identity { |identity| identities << identity }
@@ -76,8 +80,10 @@ module Authentication
76
80
  def test_identities_without_public_key_files_should_not_be_touched_if_identity_loaded_from_agent
77
81
  manager.stubs(:agent).returns(agent)
78
82
 
79
- stub_file_public_key "/first", rsa
80
- stub_file_private_key "/second", dsa, :passphrase => :should_not_be_asked
83
+ first = File.expand_path("/first")
84
+ stub_file_public_key first, rsa
85
+ second = File.expand_path("/second")
86
+ stub_file_private_key second, dsa, :passphrase => :should_not_be_asked
81
87
 
82
88
  identities = []
83
89
  manager.each_identity do |identity|
@@ -98,7 +104,8 @@ module Authentication
98
104
 
99
105
  def test_sign_with_file_originated_key_should_load_private_key_and_sign_with_it
100
106
  manager.stubs(:agent).returns(nil)
101
- stub_file_private_key "/first", rsa(512)
107
+ first = File.expand_path("/first")
108
+ stub_file_private_key first, rsa(512)
102
109
  rsa.expects(:ssh_do_sign).with("hello, world").returns("abcxyz123")
103
110
  manager.each_identity { |identity| } # preload the known_identities
104
111
  assert_equal "\0\0\0\assh-rsa\0\0\0\011abcxyz123", manager.sign(rsa, "hello, world")
data/test/test_config.rb CHANGED
@@ -3,8 +3,9 @@ require 'net/ssh/config'
3
3
 
4
4
  class TestConfig < Test::Unit::TestCase
5
5
  def test_load_for_non_existant_file_should_return_empty_hash
6
- File.expects(:readable?).with("/bogus/file").returns(false)
7
- assert_equal({}, Net::SSH::Config.load("/bogus/file", "host.name"))
6
+ bogus_file = File.expand_path("/bogus/file")
7
+ File.expects(:readable?).with(bogus_file).returns(false)
8
+ assert_equal({}, Net::SSH::Config.load(bogus_file, "host.name"))
8
9
  end
9
10
 
10
11
  def test_load_should_expand_path
@@ -2,53 +2,57 @@ require 'common'
2
2
  require 'net/ssh/key_factory'
3
3
 
4
4
  class TestKeyFactory < Test::Unit::TestCase
5
+ def setup
6
+ @key_file = File.expand_path("/key-file")
7
+ end
8
+
5
9
  def test_load_unencrypted_private_RSA_key_should_return_key
6
- File.expects(:read).with("/key-file").returns(rsa_key.export)
7
- assert_equal rsa_key.to_der, Net::SSH::KeyFactory.load_private_key("/key-file").to_der
10
+ File.expects(:read).with(@key_file).returns(rsa_key.export)
11
+ assert_equal rsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file).to_der
8
12
  end
9
13
 
10
14
  def test_load_unencrypted_private_DSA_key_should_return_key
11
- File.expects(:read).with("/key-file").returns(dsa_key.export)
12
- assert_equal dsa_key.to_der, Net::SSH::KeyFactory.load_private_key("/key-file").to_der
15
+ File.expects(:read).with(@key_file).returns(dsa_key.export)
16
+ assert_equal dsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file).to_der
13
17
  end
14
18
 
15
19
  def test_load_encrypted_private_RSA_key_should_prompt_for_password_and_return_key
16
- File.expects(:read).with("/key-file").returns(encrypted(rsa_key, "password"))
17
- Net::SSH::KeyFactory.expects(:prompt).with("Enter passphrase for /key-file:", false).returns("password")
18
- assert_equal rsa_key.to_der, Net::SSH::KeyFactory.load_private_key("/key-file").to_der
20
+ File.expects(:read).with(@key_file).returns(encrypted(rsa_key, "password"))
21
+ Net::SSH::KeyFactory.expects(:prompt).with("Enter passphrase for #{@key_file}:", false).returns("password")
22
+ assert_equal rsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file).to_der
19
23
  end
20
24
 
21
25
  def test_load_encrypted_private_RSA_key_with_password_should_not_prompt_and_return_key
22
- File.expects(:read).with("/key-file").returns(encrypted(rsa_key, "password"))
23
- assert_equal rsa_key.to_der, Net::SSH::KeyFactory.load_private_key("/key-file", "password").to_der
26
+ File.expects(:read).with(@key_file).returns(encrypted(rsa_key, "password"))
27
+ assert_equal rsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file, "password").to_der
24
28
  end
25
29
 
26
30
  def test_load_encrypted_private_DSA_key_should_prompt_for_password_and_return_key
27
- File.expects(:read).with("/key-file").returns(encrypted(dsa_key, "password"))
28
- Net::SSH::KeyFactory.expects(:prompt).with("Enter passphrase for /key-file:", false).returns("password")
29
- assert_equal dsa_key.to_der, Net::SSH::KeyFactory.load_private_key("/key-file").to_der
31
+ File.expects(:read).with(@key_file).returns(encrypted(dsa_key, "password"))
32
+ Net::SSH::KeyFactory.expects(:prompt).with("Enter passphrase for #{@key_file}:", false).returns("password")
33
+ assert_equal dsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file).to_der
30
34
  end
31
35
 
32
36
  def test_load_encrypted_private_DSA_key_with_password_should_not_prompt_and_return_key
33
- File.expects(:read).with("/key-file").returns(encrypted(dsa_key, "password"))
34
- assert_equal dsa_key.to_der, Net::SSH::KeyFactory.load_private_key("/key-file", "password").to_der
37
+ File.expects(:read).with(@key_file).returns(encrypted(dsa_key, "password"))
38
+ assert_equal dsa_key.to_der, Net::SSH::KeyFactory.load_private_key(@key_file, "password").to_der
35
39
  end
36
40
 
37
41
  def test_load_encrypted_private_key_should_give_three_tries_for_the_password_and_then_raise_exception
38
- File.expects(:read).with("/key-file").returns(encrypted(rsa_key, "password"))
39
- Net::SSH::KeyFactory.expects(:prompt).times(3).with("Enter passphrase for /key-file:", false).returns("passwod","passphrase","passwd")
40
- assert_raises(OpenSSL::PKey::RSAError) { Net::SSH::KeyFactory.load_private_key("/key-file") }
42
+ File.expects(:read).with(@key_file).returns(encrypted(rsa_key, "password"))
43
+ Net::SSH::KeyFactory.expects(:prompt).times(3).with("Enter passphrase for #{@key_file}:", false).returns("passwod","passphrase","passwd")
44
+ assert_raises(OpenSSL::PKey::RSAError) { Net::SSH::KeyFactory.load_private_key(@key_file) }
41
45
  end
42
46
 
43
47
  def test_load_encrypted_private_key_should_raise_exception_without_asking_passphrase
44
- File.expects(:read).with("/key-file").returns(encrypted(rsa_key, "password"))
48
+ File.expects(:read).with(@key_file).returns(encrypted(rsa_key, "password"))
45
49
  Net::SSH::KeyFactory.expects(:prompt).never
46
- assert_raises(OpenSSL::PKey::RSAError) { Net::SSH::KeyFactory.load_private_key("/key-file", nil, false) }
50
+ assert_raises(OpenSSL::PKey::RSAError) { Net::SSH::KeyFactory.load_private_key(@key_file, nil, false) }
47
51
  end
48
52
 
49
53
  def test_load_public_rsa_key_should_return_key
50
- File.expects(:read).with("/key-file").returns(public(rsa_key))
51
- assert_equal rsa_key.to_blob, Net::SSH::KeyFactory.load_public_key("/key-file").to_blob
54
+ File.expects(:read).with(@key_file).returns(public(rsa_key))
55
+ assert_equal rsa_key.to_blob, Net::SSH::KeyFactory.load_public_key(@key_file).to_blob
52
56
  end
53
57
 
54
58
  private
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: net-ssh
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 2.3.0
5
+ version: 2.4.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Jamis Buck
@@ -11,10 +11,20 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2012-01-11 00:00:00 Z
15
- dependencies: []
16
-
17
- description: "Net::SSH: a pure-Ruby implementation of the SSH2 client protocol."
14
+ date: 2012-05-17 00:00:00 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: jruby-pageant
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 1.0.2
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ 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."
18
28
  email:
19
29
  - net-ssh@solutious.com
20
30
  executables: []
@@ -34,6 +44,8 @@ files:
34
44
  - THANKS.rdoc
35
45
  - lib/net/ssh.rb
36
46
  - lib/net/ssh/authentication/agent.rb
47
+ - lib/net/ssh/authentication/agent/java_pageant.rb
48
+ - lib/net/ssh/authentication/agent/socket.rb
37
49
  - lib/net/ssh/authentication/constants.rb
38
50
  - lib/net/ssh/authentication/key_manager.rb
39
51
  - lib/net/ssh/authentication/methods/abstract.rb