net-ssh 2.3.0 → 2.4.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.
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