net-ssh 2.9.0 → 2.9.1

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