net-ssh 4.0.0.beta3 → 4.0.0.beta4

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