Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion lib/net/ssh/authentication/pageant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def initialize
"pageant process not running"
end

@pending_query = nil
@res = nil
@pos = 0
end
Expand Down Expand Up @@ -168,7 +169,11 @@ def send_query(query)
# Conceptually close the socket. This doesn't really do anthing
# significant, but merely complies with the Socket interface.
def close
@res = nil
# hack. probably need to move closed state to a flag,
# since ready and closed will look equivalent
# also basically we never need to actually be closed

# @res = nil
@pos = 0
end

Expand All @@ -183,6 +188,7 @@ def closed?
# is +nil+, returns all remaining data from the last query.
def read(n = nil)
return nil unless @res

if n.nil?
start, @pos = @pos, @res.size
return @res[start..-1]
Expand All @@ -192,6 +198,35 @@ def read(n = nil)
end
end

# methods we need since they dont come from buffer/buffered_io any more
def available
return @res.nil? ? 0 : @res.length - @pos
end

def read_available(n=nil)
read(n)
end

def fill(n=nil)
@res = read(n)
return @res.length
end

def enqueue(data)
@pending_query = (@pending_query || '') + data
end

def send_pending
return if @pending_query.nil?

@res = send_query(@pending_query)
@pos = 0
@pending_query = nil
end

def shutdown(reason)
#no op
end
end

# Socket changes for Ruby 1.9
Expand All @@ -202,6 +237,8 @@ class Socket19 < Socket
# process via the Windows messaging subsystem. The result is
# cached, to be returned piece-wise when #read is called.
def send_query(query)
query = @always_query || query

res = nil
filemap = 0
ptr = nil
Expand Down
15 changes: 12 additions & 3 deletions lib/net/ssh/buffered_io.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def self.extended(object) #:nodoc:
# if no data was available to be read.
def fill(n=8192)
input.consume!
data = recv(n)
data = self.respond_to?(:recv) ? recv(n) : read(n)
debug { "read #{data.length} bytes" }
input.append(data)
return data.length
Expand All @@ -71,13 +71,22 @@ def fill(n=8192)
# Read up to +length+ bytes from the input buffer. If +length+ is nil,
# all available data is read from the buffer. (See #available.)
def read_available(length=nil)
input.read(length || available)
if self.respond_to?(:recv)
input.read(length || available)
else
target = length || available
self.read(available < target ? available : target)
end
end

# Returns the number of bytes available to be read from the input buffer.
# (See #read_available.)
def available
input.available
if self.respond_to?(:recv)
input.available
else
self.available_bytes
end
end

# Enqueues data in the output buffer, to be written when #send_pending
Expand Down
14 changes: 10 additions & 4 deletions lib/net/ssh/connection/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -224,20 +224,26 @@ def preprocess
# then calls Net::SSH::Transport::Session#rekey_as_needed to allow the
# transport layer to rekey. Then returns true.
def postprocess(readers, writers)
# sometimes, got an execution hang on reader.fill.zero?
# the reader and writer socket was the same in those cases
# swapping the order so that pending writes execute before readers are closed seems to work

Array(writers).each do |writer|
writer.send_pending
end

Array(readers).each do |reader|
if listeners[reader]
listeners[reader].call(reader)
else
if reader.fill.zero?
z = reader.fill.zero?
if z
reader.close
stop_listening_to(reader)
end
end
end

Array(writers).each do |writer|
writer.send_pending
end

transport.rekey_as_needed

Expand Down
28 changes: 27 additions & 1 deletion lib/net/ssh/ruby_compat.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,34 @@ class Compat
# See: http://net-ssh.lighthouseapp.com/projects/36253/tickets/1-ioselect-threading-bug-in-ruby-18
# The root issue is documented here: http://redmine.ruby-lang.org/issues/show/1993
if RUBY_VERSION >= '1.9' || RUBY_PLATFORM == 'java'

# problem: Pageant sockets aren't real sockets/don't inherit from IO, so we can't call IO.select on them
# solution: when a Pageant socket is found in the readers/writers array, slice it out then merge it back in before returning (just assume it's always ready)
# unfortunately this fix will also need to be ported to any other library that reaches in to Net::SSH and select's its sockets (like Capistrano)
# this is slightly more verbose than necessary, but it's useful for debugging
def self.io_select(*params)
IO.select(*params)
read_array = params[0]
write_array = params[1]
error_array = params[2]
timeout = params[3]

read_sockets = read_array.nil? ? [] : read_array.reject {|s| s.class.name =~ /Pageant/ }
read_pageant = read_array.nil? ? [] : read_array.select {|s| s.class.name =~ /Pageant/ }

write_sockets = write_array.nil? ? [] : write_array.reject {|s| s.class.name =~ /Pageant/ }
write_pageant = write_array.nil? ? [] : write_array.select {|s| s.class.name =~ /Pageant/ }

result = IO.select(read_sockets, write_sockets, error_array, timeout)

if result.nil?
return nil
else
ready_read_sockets = result[0]
ready_write_sockets = result[1]
ready_error_array_only_sockets = result[2]

return [ready_read_sockets | read_pageant, ready_write_sockets | write_pageant, ready_error_array_only_sockets]
end
end
else
SELECT_MUTEX = Mutex.new
Expand Down
45 changes: 37 additions & 8 deletions lib/net/ssh/service/forward.rb
Original file line number Diff line number Diff line change
Expand Up @@ -208,15 +208,24 @@ def agent(channel)
# +client+ is a socket, +channel+ is the channel that was just created,
# and +type+ is an arbitrary string describing the type of the channel.
def prepare_client(client, channel, type)
client.extend(Net::SSH::BufferedIo)
client.extend(Net::SSH::ForwardedBufferedIo)
client.logger = logger

# TODO: consider, should there be an override of this method which is Pageant specific, even though that would mean mild reimplementation
# would mean: no shutdown in eof
# no casing around class name right here

# Since Pageant sockets aren't real sockets,
# the client object doesn't need (and shouln't have) these modules included
if (client.class.name =~ /Pageant/).nil?
client.extend(Net::SSH::BufferedIo)
client.extend(Net::SSH::ForwardedBufferedIo)
client.logger = logger
end

session.listen_to(client)
channel[:socket] = client

channel.on_data do |ch, data|
debug { "data:#{data.length} on #{type} forwarded channel" }
debug { "data: enqueueing #{data.length} bytes on #{type} forwarded channel" }
ch[:socket].enqueue(data)
end

Expand All @@ -225,7 +234,9 @@ def prepare_client(client, channel, type)
debug { "eof #{type} on #{type} forwarded channel" }
begin
ch[:socket].send_pending

ch[:socket].shutdown Socket::SHUT_WR
ch[:socket].close
rescue IOError => e
if e.message =~ /closed/ then
debug { "epipe in on_eof => shallowing exception:#{e}" }
Expand All @@ -246,13 +257,31 @@ def prepare_client(client, channel, type)
end

channel.on_process do |ch|
ch.debug { "processing #{type} forwarded connection" }

if ch[:socket].closed?
ch.info { "#{type} forwarded connection closed" }
ch.close
elsif ch[:socket].available > 0
data = ch[:socket].read_available(8192)
ch.debug { "read #{data.length} bytes from client, sending over #{type} forwarded connection" }
ch.send_data(data)
else
# For pageant sockets, the recieved and queued data (from on_data above) should be "sent" immediately
# That way, the read operation below is successful with that result

# Alternatively, instead of enqueuing the message in on_data, send_query could be called directly,
# so the pageant's response data would be available here as well.
# This requires special consideration when data is split into muliple segments (ex: carriage return split issue)
# Todo: figure out why the forwarding message starting with 000001920D... seemed to break consistantly after the first four bytes (followed by \r, in the next segment)
if ch[:socket].available == 0 && ch[:socket].class.name =~ /Pageant/
ch.debug { "Pageant socket: no data to read, so send pending" }
ch[:socket].send_pending
end

available = ch[:socket].available
if available > 0
ch.debug { "Pageant socket: now there are #{available} bytes" } if ch[:socket].class.name =~ /Pageant/
data = ch[:socket].read_available(available > 0 ? available : 8192)
ch.debug { "read #{data.length} bytes from client, sending over #{type} forwarded connection" }
ch.send_data(data)
end
end
end
end
Expand Down