net-ssh 4.0.0.beta3 → 4.0.0.beta4

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.
@@ -232,7 +232,7 @@ module Net
232
232
  identity
233
233
  end
234
234
 
235
- rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError, OpenSSL::PKey::ECError, ArgumentError => e
235
+ rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError, OpenSSL::PKey::ECError, OpenSSL::PKey::PKeyError, ArgumentError => e
236
236
  if ignore_decryption_errors
237
237
  identity
238
238
  else
@@ -13,7 +13,9 @@ else
13
13
  # For now map DL to Fiddler versus updating all the code below
14
14
  module DL
15
15
  CPtr ||= Fiddle::Pointer
16
- RUBY_FREE ||= Fiddle::RUBY_FREE
16
+ if RUBY_PLATFORM != "java"
17
+ RUBY_FREE ||= Fiddle::RUBY_FREE
18
+ end
17
19
  end
18
20
  end
19
21
 
@@ -36,7 +38,7 @@ module Net; module SSH; module Authentication
36
38
 
37
39
  # The definition of the Windows methods and data structures used in
38
40
  # communicating with the pageant process.
39
- module Win
41
+ module Win # rubocop:disable Metrics/ModuleLength
40
42
  # Compatibility on initialization
41
43
  if RUBY_VERSION < "1.9"
42
44
  extend DL::Importable
@@ -59,6 +61,11 @@ module Net; module SSH; module Authentication
59
61
  SIZEOF_DWORD = Fiddle::SIZEOF_LONG
60
62
  end
61
63
 
64
+ if RUBY_ENGINE=="jruby"
65
+ typealias("HANDLE", "void *") # From winnt.h
66
+ typealias("PHANDLE", "void *") # From winnt.h
67
+ typealias("ULONG_PTR", "unsigned long*")
68
+ end
62
69
  typealias("LPCTSTR", "char *") # From winnt.h
63
70
  typealias("LPVOID", "void *") # From winnt.h
64
71
  typealias("LPCVOID", "const void *") # From windef.h
@@ -77,16 +84,22 @@ module Net; module SSH; module Authentication
77
84
 
78
85
  SMTO_NORMAL = 0 # From winuser.h
79
86
 
87
+ SUFFIX = if RUBY_ENGINE == "jruby"
88
+ "A"
89
+ else
90
+ ""
91
+ end
92
+
80
93
  # args: lpClassName, lpWindowName
81
- extern 'HWND FindWindow(LPCTSTR, LPCTSTR)'
94
+ extern "HWND FindWindow#{SUFFIX}(LPCTSTR, LPCTSTR)"
82
95
 
83
96
  # args: none
84
97
  extern 'DWORD GetCurrentThreadId()'
85
98
 
86
99
  # args: hFile, (ignored), flProtect, dwMaximumSizeHigh,
87
100
  # dwMaximumSizeLow, lpName
88
- extern 'HANDLE CreateFileMapping(HANDLE, void *, DWORD, ' +
89
- 'DWORD, DWORD, LPCTSTR)'
101
+ extern "HANDLE CreateFileMapping#{SUFFIX}(HANDLE, void *, DWORD, ' +
102
+ 'DWORD, DWORD, LPCTSTR)"
90
103
 
91
104
  # args: hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh,
92
105
  # dwfileOffsetLow, dwNumberOfBytesToMap
@@ -99,8 +112,8 @@ module Net; module SSH; module Authentication
99
112
  extern 'BOOL CloseHandle(HANDLE)'
100
113
 
101
114
  # args: hWnd, Msg, wParam, lParam, fuFlags, uTimeout, lpdwResult
102
- extern 'LRESULT SendMessageTimeout(HWND, UINT, WPARAM, LPARAM, ' +
103
- 'UINT, UINT, PDWORD_PTR)'
115
+ extern "LRESULT SendMessageTimeout#{SUFFIX}(HWND, UINT, WPARAM, LPARAM, ' +
116
+ 'UINT, UINT, PDWORD_PTR)"
104
117
 
105
118
  # args: none
106
119
  extern 'DWORD GetLastError()'
@@ -154,7 +167,11 @@ module Net; module SSH; module Authentication
154
167
  'LPVOID Dacl']
155
168
 
156
169
  # The COPYDATASTRUCT is used to send WM_COPYDATA messages
157
- COPYDATASTRUCT = struct ['uintptr_t dwData', 'DWORD cbData', 'LPVOID lpData']
170
+ COPYDATASTRUCT = if RUBY_ENGINE == "jruby"
171
+ struct ['ULONG_PTR dwData', 'DWORD cbData', 'LPVOID lpData']
172
+ else
173
+ struct ['uintptr_t dwData', 'DWORD cbData', 'LPVOID lpData']
174
+ end
158
175
 
159
176
  # Compatibility for security attribute retrieval.
160
177
  if RUBY_VERSION < "1.9"
@@ -189,6 +206,30 @@ module Net; module SSH; module Authentication
189
206
  def self.set_ptr_data(ptr, data)
190
207
  ptr[0] = data
191
208
  end
209
+ elsif RUBY_ENGINE == "jruby"
210
+ %w(FindWindow CreateFileMapping SendMessageTimeout).each do |name|
211
+ alias_method name, name+"A"
212
+ module_function name
213
+ end
214
+ # :nodoc:
215
+ module LibC
216
+ extend FFI::Library
217
+ ffi_lib FFI::Library::LIBC
218
+ attach_function :malloc, [:size_t], :pointer
219
+ attach_function :free, [:pointer], :void
220
+ end
221
+
222
+ def self.malloc_ptr(size)
223
+ Fiddle::Pointer.new(LibC.malloc(size), size, LibC.method(:free))
224
+ end
225
+
226
+ def self.get_ptr(ptr)
227
+ return data.address
228
+ end
229
+
230
+ def self.set_ptr_data(ptr, data)
231
+ ptr.write_string_length(data, data.size)
232
+ end
192
233
  else
193
234
  def self.malloc_ptr(size)
194
235
  return DL::CPtr.malloc(size, DL::RUBY_FREE)
@@ -211,12 +252,12 @@ module Net; module SSH; module Authentication
211
252
  Win.InitializeSecurityDescriptor(psd_information,
212
253
  Win::REVISION))
213
254
  raise_error_if_zero(
214
- Win.SetSecurityDescriptorOwner(psd_information, user.SID,
255
+ Win.SetSecurityDescriptorOwner(psd_information, get_sid_ptr(user),
215
256
  0))
216
257
  raise_error_if_zero(
217
258
  Win.IsValidSecurityDescriptor(psd_information))
218
259
 
219
- sa = Win::SECURITY_ATTRIBUTES.new(malloc_ptr(Win::SECURITY_ATTRIBUTES.size))
260
+ sa = Win::SECURITY_ATTRIBUTES.new(to_struct_ptr(malloc_ptr(Win::SECURITY_ATTRIBUTES.size)))
220
261
  sa.nLength = Win::SECURITY_ATTRIBUTES.size
221
262
  sa.lpSecurityDescriptor = psd_information.to_i
222
263
  sa.bInheritHandle = 1
@@ -224,6 +265,65 @@ module Net; module SSH; module Authentication
224
265
  return sa
225
266
  end
226
267
 
268
+ if RUBY_ENGINE == "jruby"
269
+ def self.ptr_to_s(ptr, size)
270
+ ret = ptr.to_s(size)
271
+ ret << "\x00" while ret.size < size
272
+ ret
273
+ end
274
+
275
+ def self.ptr_to_handle(phandle)
276
+ phandle.ptr
277
+ end
278
+
279
+ def self.ptr_to_dword(ptr)
280
+ first = ptr.ptr.to_i
281
+ second = ptr_to_s(ptr,Win::SIZEOF_DWORD).unpack('L')[0]
282
+ raise "Error" unless first == second
283
+ first
284
+ end
285
+
286
+ def self.to_token_user(ptoken_information)
287
+ TOKEN_USER.new(ptoken_information.to_ptr)
288
+ end
289
+
290
+ def self.to_struct_ptr(ptr)
291
+ ptr.to_ptr
292
+ end
293
+
294
+ def self.get_sid(user)
295
+ ptr_to_s(user.to_ptr.ptr,Win::SIZEOF_DWORD).unpack('L')[0]
296
+ end
297
+
298
+ def self.get_sid_ptr(user)
299
+ user.to_ptr.ptr
300
+ end
301
+ else
302
+ def self.get_sid(user)
303
+ user.SID
304
+ end
305
+
306
+ def self.ptr_to_handle(phandle)
307
+ phandle.ptr.to_i
308
+ end
309
+
310
+ def self.to_struct_ptr(ptr)
311
+ ptr
312
+ end
313
+
314
+ def self.ptr_to_dword(ptr)
315
+ ptr.to_s(Win::SIZEOF_DWORD).unpack('L')[0]
316
+ end
317
+
318
+ def self.to_token_user(ptoken_information)
319
+ TOKEN_USER.new(ptoken_information)
320
+ end
321
+
322
+ def self.get_sid_ptr(user)
323
+ user.SID
324
+ end
325
+ end
326
+
227
327
  def self.get_current_user
228
328
  token_handle = open_process_token(Win.GetCurrentProcess,
229
329
  Win::TOKEN_QUERY)
@@ -238,8 +338,7 @@ module Net; module SSH; module Authentication
238
338
  raise_error_if_zero(
239
339
  Win.OpenProcessToken(process_handle, desired_access,
240
340
  ptoken_handle))
241
- token_handle = ptoken_handle.ptr.to_i
242
-
341
+ token_handle = ptr_to_handle(ptoken_handle)
243
342
  return token_handle
244
343
  end
245
344
 
@@ -254,7 +353,7 @@ module Net; module SSH; module Authentication
254
353
  Win.GetTokenInformation(token_handle,
255
354
  token_information_class,
256
355
  Win::NULL, 0, preturn_length)
257
- ptoken_information = malloc_ptr(preturn_length.to_s(Win::SIZEOF_DWORD).unpack('L')[0])
356
+ ptoken_information = malloc_ptr(ptr_to_dword(preturn_length))
258
357
 
259
358
  # This call is going to write the requested information to
260
359
  # the memory location referenced by token_information.
@@ -265,7 +364,7 @@ module Net; module SSH; module Authentication
265
364
  ptoken_information.size,
266
365
  preturn_length))
267
366
 
268
- return TOKEN_USER.new(ptoken_information)
367
+ return to_token_user(ptoken_information)
269
368
  end
270
369
 
271
370
  def self.raise_error_if_zero(result)
@@ -288,10 +387,8 @@ module Net; module SSH; module Authentication
288
387
 
289
388
  private_class_method :new
290
389
 
291
- # The factory method for creating a new Socket instance. The location
292
- # parameter is ignored, and is only needed for compatibility with
293
- # the general Socket interface.
294
- def self.open(location=nil)
390
+ # The factory method for creating a new Socket instance.
391
+ def self.open
295
392
  new
296
393
  end
297
394
 
@@ -300,7 +397,7 @@ module Net; module SSH; module Authentication
300
397
  def initialize
301
398
  @win = Win.FindWindow("Pageant", "Pageant")
302
399
 
303
- if @win == 0
400
+ if @win.to_i == 0
304
401
  raise Net::SSH::Exception,
305
402
  "pageant process not running"
306
403
  end
@@ -66,6 +66,9 @@ module Net; module SSH
66
66
  debug { "read #{data.length} bytes" }
67
67
  input.append(data)
68
68
  return data.length
69
+ rescue EOFError => e
70
+ @input_errors << e
71
+ return 0
69
72
  end
70
73
 
71
74
  # Read up to +length+ bytes from the input buffer. If +length+ is nil,
@@ -143,21 +146,23 @@ module Net; module SSH
143
146
  # Module#include to add this module.
144
147
  def initialize_buffered_io
145
148
  @input = Net::SSH::Buffer.new
149
+ @input_errors = []
146
150
  @output = Net::SSH::Buffer.new
151
+ @output_errors = []
147
152
  end
148
153
  end
149
154
 
150
155
 
151
-
156
+
152
157
  # Fixes for two issues by Miklós Fazekas:
153
158
  #
154
- # * if client closes a forwarded connection, but the server is
159
+ # * if client closes a forwarded connection, but the server is
155
160
  # reading, net-ssh terminates with IOError socket closed.
156
- # * if client force closes (RST) a forwarded connection, but
161
+ # * if client force closes (RST) a forwarded connection, but
157
162
  # server is reading, net-ssh terminates with [an exception]
158
163
  #
159
- # See:
160
- #
164
+ # See:
165
+ #
161
166
  # http://net-ssh.lighthouseapp.com/projects/36253/tickets/7
162
167
  # http://github.com/net-ssh/net-ssh/tree/portfwfix
163
168
  #
@@ -168,24 +173,24 @@ module Net; module SSH
168
173
  rescue Errno::ECONNRESET => e
169
174
  debug { "connection was reset => shallowing exception:#{e}" }
170
175
  return 0
171
- rescue IOError => e
172
- if e.message =~ /closed/ then
176
+ rescue IOError => e
177
+ if e.message =~ /closed/ then
173
178
  debug { "connection was reset => shallowing exception:#{e}" }
174
179
  return 0
175
180
  else
176
181
  raise
177
- end
182
+ end
178
183
  end
179
184
  end
180
-
185
+
181
186
  def send_pending
182
187
  begin
183
- super
188
+ super
184
189
  rescue Errno::ECONNRESET => e
185
190
  debug { "connection was reset => shallowing exception:#{e}" }
186
191
  return 0
187
192
  rescue IOError => e
188
- if e.message =~ /closed/ then
193
+ if e.message =~ /closed/ then
189
194
  debug { "connection was reset => shallowing exception:#{e}" }
190
195
  return 0
191
196
  else
@@ -194,5 +199,5 @@ module Net; module SSH
194
199
  end
195
200
  end
196
201
  end
197
-
202
+
198
203
  end; end
@@ -234,7 +234,7 @@ module Net; module SSH; module Connection
234
234
  # Called by event loop to process available data before going to
235
235
  # event multiplexing
236
236
  def ev_preprocess(&block)
237
- dispatch_incoming_packets
237
+ dispatch_incoming_packets(raise_disconnect_errors: false)
238
238
  each_channel { |id, channel| channel.process unless channel.local_closed? }
239
239
  end
240
240
 
@@ -335,6 +335,15 @@ module Net; module SSH; module Connection
335
335
  channels[local_id] = channel
336
336
  end
337
337
 
338
+ class StringWithExitstatus < String
339
+ def initialize(str, exitstatus)
340
+ super(str)
341
+ @exitstatus = exitstatus
342
+ end
343
+
344
+ attr_reader :exitstatus
345
+ end
346
+
338
347
  # A convenience method for executing a command and interacting with it. If
339
348
  # no block is given, all output is printed via $stdout and $stderr. Otherwise,
340
349
  # the block is called for each data and extended data packet, with three
@@ -355,11 +364,21 @@ module Net; module SSH; module Connection
355
364
  # puts data
356
365
  # end
357
366
  # end
358
- def exec(command, &block)
367
+ def exec(command, status: nil, &block)
359
368
  open_channel do |channel|
360
369
  channel.exec(command) do |ch, success|
361
370
  raise "could not execute command: #{command.inspect}" unless success
362
371
 
372
+ if status
373
+ channel.on_request("exit-status") do |ch2,data|
374
+ status[:exit_code] = data.read_long
375
+ end
376
+
377
+ channel.on_request("exit-signal") do |ch2, data|
378
+ status[:exit_signal] = data.read_long
379
+ end
380
+ end
381
+
363
382
  channel.on_data do |ch2, data|
364
383
  if block
365
384
  block.call(ch2, :stdout, data)
@@ -384,19 +403,22 @@ module Net; module SSH; module Connection
384
403
  # as a single string.
385
404
  #
386
405
  # matches = ssh.exec!("grep something /some/files")
387
- def exec!(command, &block)
406
+ #
407
+ # the returned string has an exitstatus method to query it's exit satus
408
+ def exec!(command, status: nil, &block)
388
409
  block_or_concat = block || Proc.new do |ch, type, data|
389
410
  ch[:result] ||= ""
390
411
  ch[:result] << data
391
412
  end
392
413
 
393
- channel = exec(command, &block_or_concat)
414
+ status ||= {}
415
+ channel = exec(command, status: status, &block_or_concat)
394
416
  channel.wait
395
417
 
396
418
  channel[:result] ||= "" unless block
397
419
  channel[:result] &&= channel[:result].force_encoding("UTF-8") unless block
398
420
 
399
- return channel[:result]
421
+ StringWithExitstatus.new(channel[:result], status[:exit_code]) if channel[:result]
400
422
  end
401
423
 
402
424
  # Enqueues a message to be sent to the server as soon as the socket is
@@ -509,7 +531,7 @@ module Net; module SSH; module Connection
509
531
 
510
532
  # Read all pending packets from the connection and dispatch them as
511
533
  # appropriate. Returns as soon as there are no more pending packets.
512
- def dispatch_incoming_packets
534
+ def dispatch_incoming_packets(raise_disconnect_errors: true)
513
535
  while packet = transport.poll_message
514
536
  unless MAP.key?(packet.type)
515
537
  raise Net::SSH::Exception, "unexpected response #{packet.type} (#{packet.inspect})"
@@ -519,7 +541,7 @@ module Net; module SSH; module Connection
519
541
  end
520
542
  rescue
521
543
  force_channel_cleanup_on_close if closed?
522
- raise
544
+ raise if raise_disconnect_errors || !$!.is_a?(Net::SSH::Disconnect)
523
545
  end
524
546
 
525
547
  # Returns the next available channel id to be assigned, and increments
@@ -49,7 +49,7 @@ module Net; module SSH
49
49
  # encrypted (requiring a passphrase to use), the user will be
50
50
  # prompted to enter their password unless passphrase works.
51
51
  def load_data_private_key(data, passphrase=nil, ask_passphrase=true, filename="", prompt=Prompt.default)
52
- key_read, error_class = classify_key(data, filename)
52
+ key_read, error_classes = classify_key(data, filename)
53
53
 
54
54
  encrypted_key = data.match(/ENCRYPTED/)
55
55
  tries = 0
@@ -58,7 +58,7 @@ module Net; module SSH
58
58
  result =
59
59
  begin
60
60
  key_read[data, passphrase || 'invalid']
61
- rescue error_class
61
+ rescue *error_classes
62
62
  if encrypted_key && ask_passphrase
63
63
  tries += 1
64
64
  if tries <= 3
@@ -110,18 +110,18 @@ module Net; module SSH
110
110
  def classify_key(data, filename)
111
111
  if data.match(/-----BEGIN OPENSSH PRIVATE KEY-----/)
112
112
  if defined?(Net::SSH::Authentication::ED25519)
113
- return ->(key_data, passphrase) { Net::SSH::Authentication::ED25519::PrivKey.read(key_data, passphrase) }, ArgumentError
113
+ return ->(key_data, passphrase) { Net::SSH::Authentication::ED25519::PrivKey.read(key_data, passphrase) }, [ArgumentError]
114
114
  else
115
115
  raise OpenSSL::PKey::PKeyError, "OpenSSH keys only supported if ED25519 is available - #{ED25519_LOAD_ERROR}"
116
116
  end
117
117
  elsif OpenSSL::PKey.respond_to?(:read)
118
- return ->(key_data, passphrase) { OpenSSL::PKey.read(key_data, passphrase) }, ArgumentError
118
+ return ->(key_data, passphrase) { OpenSSL::PKey.read(key_data, passphrase) }, [ArgumentError, OpenSSL::PKey::PKeyError]
119
119
  elsif data.match(/-----BEGIN DSA PRIVATE KEY-----/)
120
- return ->(key_data, passphrase) { OpenSSL::PKey::DSA.new(key_data, passphrase) }, OpenSSL::PKey::DSAError
120
+ return ->(key_data, passphrase) { OpenSSL::PKey::DSA.new(key_data, passphrase) }, [OpenSSL::PKey::DSAError]
121
121
  elsif data.match(/-----BEGIN RSA PRIVATE KEY-----/)
122
- return ->(key_data, passphrase) { OpenSSL::PKey::RSA.new(key_data, passphrase) }, OpenSSL::PKey::RSAError
122
+ return ->(key_data, passphrase) { OpenSSL::PKey::RSA.new(key_data, passphrase) }, [OpenSSL::PKey::RSAError]
123
123
  elsif data.match(/-----BEGIN EC PRIVATE KEY-----/) && defined?(OpenSSL::PKey::EC)
124
- return ->(key_data, passphrase) { OpenSSL::PKey::EC.new(key_data, passphrase) }, OpenSSL::PKey::ECError
124
+ return ->(key_data, passphrase) { OpenSSL::PKey::EC.new(key_data, passphrase) }, [OpenSSL::PKey::ECError]
125
125
  elsif data.match(/-----BEGIN (.+) PRIVATE KEY-----/)
126
126
  raise OpenSSL::PKey::PKeyError, "not a supported key type '#{$1}'"
127
127
  else