net-ssh 2.9.0 → 2.9.1

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.tar.gz.sig CHANGED
Binary file
@@ -2,7 +2,6 @@ language: ruby
2
2
  rvm:
3
3
  - "1.9.3"
4
4
  - "2.0.0"
5
- - "2.1.0"
6
5
 
7
6
 
8
7
  install: gem install jeweler test-unit mocha
@@ -1,5 +1,11 @@
1
1
 
2
2
 
3
+ === 2.9.1 / 13 May 2014
4
+
5
+ * Fix for unknown response from agent on Windows with 64-bit PuTTY [chrahunt]
6
+ * Support negative patterns in host lookup from the SSH config file [nirvdrum]
7
+
8
+
3
9
  === 2.9.0 / 30 Apr 2014
4
10
 
5
11
  * New ciphers [chr4]
@@ -27,15 +27,21 @@ module Net; module SSH; module Authentication
27
27
  # The definition of the Windows methods and data structures used in
28
28
  # communicating with the pageant process.
29
29
  module Win
30
+ # Compatibility on initialization
30
31
  if RUBY_VERSION < "1.9"
31
32
  extend DL::Importable
32
33
 
33
34
  dlload 'user32'
34
35
  dlload 'kernel32'
36
+ dlload 'advapi32'
37
+
38
+ SIZEOF_DWORD = DL.sizeof('L')
35
39
  else
36
40
  extend DL::Importer
37
- dlload 'user32','kernel32'
41
+ dlload 'user32','kernel32', 'advapi32'
38
42
  include DL::Win32Types
43
+
44
+ SIZEOF_DWORD = DL::SIZEOF_LONG
39
45
  end
40
46
 
41
47
  typealias("LPCTSTR", "char *") # From winnt.h
@@ -45,6 +51,7 @@ module Net; module SSH; module Authentication
45
51
  typealias("WPARAM", "unsigned int *") # From windef.h
46
52
  typealias("LPARAM", "long *") # From windef.h
47
53
  typealias("PDWORD_PTR", "long *") # From basetsd.h
54
+ typealias("USHORT", "unsigned short") # From windef.h
48
55
 
49
56
  # From winbase.h, winnt.h
50
57
  INVALID_HANDLE_VALUE = -1
@@ -63,8 +70,8 @@ module Net; module SSH; module Authentication
63
70
 
64
71
  # args: hFile, (ignored), flProtect, dwMaximumSizeHigh,
65
72
  # dwMaximumSizeLow, lpName
66
- extern 'HANDLE CreateFileMapping(HANDLE, void *, DWORD, DWORD, ' +
67
- 'DWORD, LPCTSTR)'
73
+ extern 'HANDLE CreateFileMapping(HANDLE, void *, DWORD, ' +
74
+ 'DWORD, DWORD, LPCTSTR)'
68
75
 
69
76
  # args: hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh,
70
77
  # dwfileOffsetLow, dwNumberOfBytesToMap
@@ -79,9 +86,180 @@ module Net; module SSH; module Authentication
79
86
  # args: hWnd, Msg, wParam, lParam, fuFlags, uTimeout, lpdwResult
80
87
  extern 'LRESULT SendMessageTimeout(HWND, UINT, WPARAM, LPARAM, ' +
81
88
  'UINT, UINT, PDWORD_PTR)'
89
+
90
+ # args: none
91
+ extern 'DWORD GetLastError()'
92
+
93
+ # args: none
94
+ extern 'HANDLE GetCurrentProcess()'
95
+
96
+ # args: hProcessHandle, dwDesiredAccess, (out) phNewTokenHandle
97
+ extern 'BOOL OpenProcessToken(HANDLE, DWORD, PHANDLE)'
98
+
99
+ # args: hTokenHandle, uTokenInformationClass,
100
+ # (out) lpTokenInformation, dwTokenInformationLength
101
+ # (out) pdwInfoReturnLength
102
+ extern 'BOOL GetTokenInformation(HANDLE, UINT, LPVOID, DWORD, ' +
103
+ 'PDWORD)'
104
+
105
+ # args: (out) lpSecurityDescriptor, dwRevisionLevel
106
+ extern 'BOOL InitializeSecurityDescriptor(LPVOID, DWORD)'
107
+
108
+ # args: (out) lpSecurityDescriptor, lpOwnerSid, bOwnerDefaulted
109
+ extern 'BOOL SetSecurityDescriptorOwner(LPVOID, LPVOID, BOOL)'
110
+
111
+ # args: pSecurityDescriptor
112
+ extern 'BOOL IsValidSecurityDescriptor(LPVOID)'
113
+
114
+ # Constants needed for security attribute retrieval.
115
+ # Specifies the access mask corresponding to the desired access
116
+ # rights.
117
+ TOKEN_QUERY = 0x8
118
+
119
+ # The value of TOKEN_USER from the TOKEN_INFORMATION_CLASS enum.
120
+ TOKEN_USER_INFORMATION_CLASS = 1
121
+
122
+ # The initial revision level assigned to the security descriptor.
123
+ REVISION = 1
124
+
125
+ # Structs for security attribute functions.
126
+ # Holds the retrieved user access token.
127
+ TOKEN_USER = struct ['void * SID', 'DWORD ATTRIBUTES']
128
+
129
+ # Contains the security descriptor, this gets passed to the
130
+ # function that constructs the shared memory map.
131
+ SECURITY_ATTRIBUTES = struct ['DWORD nLength',
132
+ 'LPVOID lpSecurityDescriptor',
133
+ 'BOOL bInheritHandle']
134
+
135
+ # The security descriptor holds security information.
136
+ SECURITY_DESCRIPTOR = struct ['UCHAR Revision', 'UCHAR Sbz1',
137
+ 'USHORT Control', 'LPVOID Owner',
138
+ 'LPVOID Group', 'LPVOID Sacl',
139
+ 'LPVOID Dacl']
140
+
141
+ # Compatibility for security attribute retrieval.
82
142
  if RUBY_VERSION < "1.9"
83
- alias_method :FindWindow,:findWindow
84
- module_function :FindWindow
143
+ # Alias functions to > 1.9 capitalization
144
+ %w(findWindow
145
+ getCurrentProcess
146
+ initializeSecurityDescriptor
147
+ setSecurityDescriptorOwner
148
+ isValidSecurityDescriptor
149
+ openProcessToken
150
+ getTokenInformation
151
+ getLastError
152
+ getCurrentThreadId
153
+ createFileMapping
154
+ mapViewOfFile
155
+ sendMessageTimeout
156
+ unmapViewOfFile
157
+ closeHandle).each do |name|
158
+ new_name = name[0].chr.upcase + name[1..name.length]
159
+ alias_method new_name, name
160
+ module_function new_name
161
+ end
162
+
163
+ def self.malloc_ptr(size)
164
+ return DL.malloc(size)
165
+ end
166
+
167
+ def self.get_ptr(data)
168
+ return data.to_ptr
169
+ end
170
+
171
+ def self.set_ptr_data(ptr, data)
172
+ ptr[0] = data
173
+ end
174
+ else
175
+ def self.malloc_ptr(size)
176
+ return DL::CPtr.malloc(size, DL::RUBY_FREE)
177
+ end
178
+
179
+ def self.get_ptr(data)
180
+ return DL::CPtr.to_ptr data
181
+ end
182
+
183
+ def self.set_ptr_data(ptr, data)
184
+ DL::CPtr.new(ptr)[0,data.size] = data
185
+ end
186
+ end
187
+
188
+ def self.get_security_attributes_for_user
189
+ user = get_current_user
190
+
191
+ psd_information = malloc_ptr(Win::SECURITY_DESCRIPTOR.size)
192
+ raise_error_if_zero(
193
+ Win.InitializeSecurityDescriptor(psd_information,
194
+ Win::REVISION))
195
+ raise_error_if_zero(
196
+ Win.SetSecurityDescriptorOwner(psd_information, user.SID,
197
+ 0))
198
+ raise_error_if_zero(
199
+ Win.IsValidSecurityDescriptor(psd_information))
200
+
201
+ nLength = Win::SECURITY_ATTRIBUTES.size
202
+ lpSecurityDescriptor = psd_information
203
+ bInheritHandle = 1
204
+ sa = [nLength, lpSecurityDescriptor.to_i,
205
+ bInheritHandle].pack("LLC")
206
+
207
+ return sa
208
+ end
209
+
210
+ def self.get_current_user
211
+ token_handle = open_process_token(Win.GetCurrentProcess,
212
+ Win::TOKEN_QUERY)
213
+ token_user = get_token_information(token_handle,
214
+ Win::TOKEN_USER_INFORMATION_CLASS)
215
+ return token_user
216
+ end
217
+
218
+ def self.open_process_token(process_handle, desired_access)
219
+ ptoken_handle = malloc_ptr(Win::SIZEOF_DWORD)
220
+
221
+ raise_error_if_zero(
222
+ Win.OpenProcessToken(process_handle, desired_access,
223
+ ptoken_handle))
224
+ token_handle = ptoken_handle.ptr.to_i
225
+
226
+ return token_handle
227
+ end
228
+
229
+ def self.get_token_information(token_handle,
230
+ token_information_class)
231
+ # Hold the size of the information to be returned
232
+ preturn_length = malloc_ptr(Win::SIZEOF_DWORD)
233
+
234
+ # Going to throw an INSUFFICIENT_BUFFER_ERROR, but that is ok
235
+ # here. This is retrieving the size of the information to be
236
+ # returned.
237
+ Win.GetTokenInformation(token_handle,
238
+ token_information_class,
239
+ Win::NULL, 0, preturn_length)
240
+ ptoken_information = malloc_ptr(preturn_length.ptr.to_i)
241
+
242
+ # This call is going to write the requested information to
243
+ # the memory location referenced by token_information.
244
+ raise_error_if_zero(
245
+ Win.GetTokenInformation(token_handle,
246
+ token_information_class,
247
+ ptoken_information,
248
+ ptoken_information.size,
249
+ preturn_length))
250
+
251
+ return TOKEN_USER.new(ptoken_information)
252
+ end
253
+
254
+ def self.raise_error_if_zero(result)
255
+ if result == 0
256
+ raise "Windows error: #{Win.GetLastError}"
257
+ end
258
+ end
259
+
260
+ # Get a null-terminated string given a string.
261
+ def self.get_cstr(str)
262
+ return str + "\000"
85
263
  end
86
264
  end
87
265
 
@@ -140,78 +318,27 @@ module Net; module SSH; module Authentication
140
318
 
141
319
  def close
142
320
  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
-
152
- # Packages the given query string and sends it to the pageant
153
- # process via the Windows messaging subsystem. The result is
154
- # cached, to be returned piece-wise when #read is called.
155
- def send_query_18(query)
156
- res = nil
157
- filemap = 0
158
- ptr = nil
159
- id = DL::PtrData.malloc(DL.sizeof("L"))
160
-
161
- mapname = "PageantRequest%08x\000" % Win.getCurrentThreadId()
162
- filemap = Win.createFileMapping(Win::INVALID_HANDLE_VALUE,
163
- Win::NULL,
164
- Win::PAGE_READWRITE, 0,
165
- AGENT_MAX_MSGLEN, mapname)
166
- if filemap == 0
167
- raise Net::SSH::Exception,
168
- "Creation of file mapping failed"
169
- end
170
-
171
- ptr = Win.mapViewOfFile(filemap, Win::FILE_MAP_WRITE, 0, 0,
172
- AGENT_MAX_MSGLEN)
173
-
174
- if ptr.nil? || ptr.null?
175
- raise Net::SSH::Exception, "Mapping of file failed"
176
- end
177
-
178
- ptr[0] = query
179
-
180
- cds = [AGENT_COPYDATA_ID, mapname.size + 1, mapname].
181
- pack("LLp").to_ptr
182
- succ = Win.sendMessageTimeout(@win, Win::WM_COPYDATA, Win::NULL,
183
- cds, Win::SMTO_NORMAL, 5000, id)
184
-
185
- if succ > 0
186
- retlen = 4 + ptr.to_s(4).unpack("N")[0]
187
- res = ptr.to_s(retlen)
188
- end
189
-
190
- return res
191
- ensure
192
- Win.unmapViewOfFile(ptr) unless ptr.nil? || ptr.null?
193
- Win.closeHandle(filemap) if filemap != 0
194
- end
195
321
 
196
322
  # Packages the given query string and sends it to the pageant
197
323
  # process via the Windows messaging subsystem. The result is
198
324
  # cached, to be returned piece-wise when #read is called.
199
- def send_query_19(query)
325
+ def send_query(query)
200
326
  res = nil
201
327
  filemap = 0
202
328
  ptr = nil
203
- id = DL.malloc(DL::SIZEOF_LONG)
329
+ id = Win.malloc_ptr(Win::SIZEOF_DWORD)
204
330
 
205
- mapname = "PageantRequest%08x\000" % Win.GetCurrentThreadId()
331
+ mapname = "PageantRequest%08x" % Win.GetCurrentThreadId()
332
+ security_attributes = Win.get_ptr Win.get_security_attributes_for_user
206
333
 
207
- filemap = Win.CreateFileMapping(Win::INVALID_HANDLE_VALUE,
208
- Win::NULL,
209
- Win::PAGE_READWRITE, 0,
334
+ filemap = Win.CreateFileMapping(Win::INVALID_HANDLE_VALUE,
335
+ security_attributes,
336
+ Win::PAGE_READWRITE, 0,
210
337
  AGENT_MAX_MSGLEN, mapname)
211
338
 
212
339
  if filemap == 0 || filemap == Win::INVALID_HANDLE_VALUE
213
340
  raise Net::SSH::Exception,
214
- "Creation of file mapping failed"
341
+ "Creation of file mapping failed with error: #{Win.GetLastError}"
215
342
  end
216
343
 
217
344
  ptr = Win.MapViewOfFile(filemap, Win::FILE_MAP_WRITE, 0, 0,
@@ -221,17 +348,19 @@ module Net; module SSH; module Authentication
221
348
  raise Net::SSH::Exception, "Mapping of file failed"
222
349
  end
223
350
 
224
- DL::CPtr.new(ptr)[0,query.size]=query
351
+ Win.set_ptr_data(ptr, query)
225
352
 
226
- cds = DL::CPtr.to_ptr [AGENT_COPYDATA_ID, mapname.size + 1, mapname].
227
- pack("LLp")
353
+ cds = Win.get_ptr [AGENT_COPYDATA_ID, mapname.size + 1,
354
+ Win.get_cstr(mapname)].pack("LLp")
228
355
  succ = Win.SendMessageTimeout(@win, Win::WM_COPYDATA, Win::NULL,
229
356
  cds, Win::SMTO_NORMAL, 5000, id)
230
357
 
231
358
  if succ > 0
232
359
  retlen = 4 + ptr.to_s(4).unpack("N")[0]
233
360
  res = ptr.to_s(retlen)
234
- end
361
+ else
362
+ raise Net::SSH::Exception, "Message failed with error: #{Win.GetLastError}"
363
+ end
235
364
 
236
365
  return res
237
366
  ensure
@@ -255,7 +255,7 @@ module Net; module SSH
255
255
  key.e = read_bignum
256
256
  key.n = read_bignum
257
257
 
258
- when /^ecdsa\-sha2\-(\w*)$/
258
+ when /^ecdsa\-sha2\-(\w*)$/, /^ssh-ed25519(-cert-v01@openssh.com)?$/
259
259
  unless defined?(OpenSSL::PKey::EC)
260
260
  raise NotImplementedError, "unsupported key type `#{type}'"
261
261
  else
@@ -103,8 +103,18 @@ module Net; module SSH
103
103
  if key == 'host'
104
104
  # Support "Host host1 host2 hostN".
105
105
  # See http://github.com/net-ssh/net-ssh/issues#issue/6
106
- multi_host = value.to_s.split(/\s+/)
107
- matched_host = multi_host.select { |h| host =~ pattern2regex(h) }.first
106
+ negative_hosts, positive_hosts = value.to_s.split(/\s+/).partition { |h| h.start_with?('!') }
107
+
108
+ # Check for negative patterns first. If the host matches, that overrules any other positive match.
109
+ # The host substring code is used to strip out the starting "!" so the regexp will be correct.
110
+ negative_match = negative_hosts.select { |h| host =~ pattern2regex(h[1..-1]) }.first
111
+
112
+ if negative_match
113
+ matched_host = nil
114
+ else
115
+ matched_host = positive_hosts.select { |h| host =~ pattern2regex(h) }.first
116
+ end
117
+
108
118
  seen_host = true
109
119
  settings[key] = host
110
120
  elsif !seen_host
@@ -41,6 +41,9 @@ module Net; module SSH; module Transport
41
41
  "camellia192-ctr@openssh.org" => "camellia-192-ecb",
42
42
  "camellia256-ctr@openssh.org" => "camellia-256-ecb",
43
43
 
44
+ "aes256-gcm@openssh.com" => "aes-256-gcm",
45
+ "aes128-gcm@openssh.com" => "aes-128-gcm",
46
+
44
47
  "none" => "none",
45
48
  }
46
49
 
@@ -51,7 +51,7 @@ module Net; module SSH
51
51
  MINOR = 9
52
52
 
53
53
  # The tiny component of this version of the Net::SSH library
54
- TINY = 0
54
+ TINY = 1
55
55
 
56
56
  # The current version of the Net::SSH library as a Version instance
57
57
  CURRENT = new(MAJOR, MINOR, TINY)
@@ -5,12 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "net-ssh"
8
- s.version = "2.9.0"
8
+ s.version = "2.9.1"
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
12
  s.cert_chain = ["gem-public_cert.pem"]
13
- s.date = "2014-04-30"
13
+ s.date = "2014-05-13"
14
14
  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."
15
15
  s.email = "net-ssh@solutious.com"
16
16
  s.extra_rdoc_files = [
@@ -129,6 +129,7 @@ Gem::Specification.new do |s|
129
129
  "test/configs/exact_match",
130
130
  "test/configs/host_plus",
131
131
  "test/configs/multihost",
132
+ "test/configs/negative_match",
132
133
  "test/configs/nohost",
133
134
  "test/configs/numeric_host",
134
135
  "test/configs/send_env",
@@ -138,6 +139,7 @@ Gem::Specification.new do |s|
138
139
  "test/connection/test_session.rb",
139
140
  "test/known_hosts/github",
140
141
  "test/manual/test_forward.rb",
142
+ "test/manual/test_pageant.rb",
141
143
  "test/start/test_connection.rb",
142
144
  "test/start/test_options.rb",
143
145
  "test/start/test_transport.rb",
@@ -0,0 +1,6 @@
1
+ Host test.* !test.host
2
+ Port 1234
3
+ Compression no
4
+
5
+ Host test.host
6
+ Port 9876
@@ -0,0 +1,37 @@
1
+ #
2
+ # Tests for communication capability with Pageant (or KeeAgent)
3
+ # process, to include the case where it is running in different UAC
4
+ # context.
5
+ #
6
+ # To run:
7
+ # - Ensure that Pageant is running (not as administrator).
8
+ # - Open two command prompts, one as an administrator and one limited
9
+ # (normal).
10
+ # - Within each, from the root net/ssh project directory, execute:
11
+ # ruby -Ilib -Itest -rrubygems test/manual/test_pageant.rb
12
+ #
13
+
14
+ require 'common'
15
+ require 'net/ssh/authentication/agent'
16
+
17
+ module Authentication
18
+
19
+ class TestPageant < Test::Unit::TestCase
20
+
21
+ def test_agent_should_be_able_to_negotiate
22
+ assert_nothing_raised(Net::SSH::Authentication::AgentNotAvailable) { agent.negotiate! }
23
+ end
24
+
25
+ private
26
+
27
+ def agent(auto=:connect)
28
+ @agent ||= begin
29
+ agent = Net::SSH::Authentication::Agent.new
30
+ agent.connect! if auto == :connect
31
+ agent
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -35,6 +35,12 @@ class TestConfig < Test::Unit::TestCase
35
35
  assert !config.key?('rekeylimit')
36
36
  end
37
37
 
38
+ def test_load_with_wild_card_and_negative_pattern_does_not_match
39
+ config = Net::SSH::Config.load(config(:negative_match), "test.host")
40
+ assert_equal 9876, config['port']
41
+ assert !config.key?('compression')
42
+ end
43
+
38
44
  def test_for_should_load_all_files_and_translate_to_net_ssh_options
39
45
  config = Net::SSH::Config.for("test.host", [config(:exact_match), config(:wild_cards)])
40
46
  assert_equal 1234, config[:port]
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.9.0
4
+ version: 2.9.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -38,7 +38,7 @@ cert_chain:
38
38
  bkh3ZjNJSVVQZzdsc01DUnpPa1EvRkQ4N0JJM1YzU25GTm9UQ2RHZ25HajNq
39
39
  Zlc0elJsTAppRnlhcmVGUEE4NGJ0UT09Ci0tLS0tRU5EIENFUlRJRklDQVRF
40
40
  LS0tLS0K
41
- date: 2014-04-30 00:00:00.000000000 Z
41
+ date: 2014-05-13 00:00:00.000000000 Z
42
42
  dependencies:
43
43
  - !ruby/object:Gem::Dependency
44
44
  name: test-unit
@@ -193,6 +193,7 @@ files:
193
193
  - test/configs/exact_match
194
194
  - test/configs/host_plus
195
195
  - test/configs/multihost
196
+ - test/configs/negative_match
196
197
  - test/configs/nohost
197
198
  - test/configs/numeric_host
198
199
  - test/configs/send_env
@@ -202,6 +203,7 @@ files:
202
203
  - test/connection/test_session.rb
203
204
  - test/known_hosts/github
204
205
  - test/manual/test_forward.rb
206
+ - test/manual/test_pageant.rb
205
207
  - test/start/test_connection.rb
206
208
  - test/start/test_options.rb
207
209
  - test/start/test_transport.rb
metadata.gz.sig CHANGED
Binary file