class EventMachine::Ssh::Connection
EventMachine::Ssh::Connection
is a EventMachine::Connection that emulates the Net::SSH
transport layer. It ties itself into Net::SSH
so that the EventMachine
reactor loop can take the place of the Net::SSH
event loop. Most of the methods here are only for compatibility with Net::SSH
Constants
- TIMEOUT
maximum number of seconds to wait for a connection
Attributes
The Algorithms instance used to perform key exchanges.
@return [String] The host to connect to, as given to the constructor.
The host-key verifier object used to verify host keys, to ensure that the connection is not being spoofed.
The hash of options that were given to the object at initialization.
@return [Fixnum] the port number (DEFAULT_PORT) to connect to, as given in the options to the constructor.
@return [ServerVersion] The ServerVersion
instance that encapsulates the negotiated protocol version.
@return [PacketStream] emulates a socket and ssh packetstream
Public Class Methods
# File lib/em-ssh/connection.rb, line 148 def initialize(options = {}) debug("#{self.class}.new(#{options})") @host = options[:host] @port = options[:port] @password = options[:password] @queue = [] @options = options @timeout = options[:timeout] || TIMEOUT begin on(:connected) do |session| @connected = true succeed(session, @host) end on(:error) do |e| fail(e) close_connection end @nocon = on(:closed) do fail(ConnectionFailed.new(@host)) close_connection end @contimeout = EM::Timer.new(@timeout) do fail(ConnectionTimeout.new(@host)) close_connection end @negotimeout = EM::Timer.new(options[:nego_timeout] || @timeout) do fail(NegotiationTimeout.new(@host)) end @algotimeout = EM::Timer.new(options[:nego_timeout] || @timeout) do fail(NegotiationTimeout.new(@host)) end @nonego = on(:closed) do fail(ConnectionTerminated.new(@host)) close_connection end on(:version_negotiated) { @nonego.cancel } @error_callback = lambda do |code| fail(SshError.new(code)) close_connection end @host_key_verifier = select_host_key_verifier(options[:paranoid]) @server_version = ServerVersion.new(self) on(:version_negotiated) do @negotimeout.cancel @negotimeout = nil @data.consume!(@server_version.header.length) @algorithms = Net::SSH::Transport::Algorithms.new(self, options) register_data_handler on_next(:algo_init) do @algotimeout.cancel @algotimeout = nil auth = AuthenticationSession.new(self, options) user = options.fetch(:user, user) Fiber.new do if auth.authenticate("ssh-connection", user, options[:password]) fire(:connected, Session.new(self, options)) else fire(:error, Net::SSH::AuthenticationFailed.new(user)) end # auth.authenticate("ssh-connection", user, options[:password]) end.resume # Fiber end # :algo_init # Some ssh servers, e.g. DropBear send the algo data immediately after sending the server version # string without waiting for the client. In those cases the data buffer will now contain the algo # strings, so we can't wait for the next call to #receive_data. We simulate it here to move the # process forward. receive_data("") unless @data.eof? end # :version_negotiated rescue Exception => e log.fatal("caught an error during initialization: #{e}\n #{e.backtrace.join("\n ")}") fail(e) end # begin self end
Public Instance Methods
Close the connection
# File lib/em-ssh/connection.rb, line 43 def close # #unbind will update @closed close_connection end
@return [Boolean] true if the connection has been closed
# File lib/em-ssh/connection.rb, line 38 def closed? @closed == true end
Configure's the packet stream's client state with the given set of options. This is typically used to define the cipher, compression, and hmac algorithms to use when sending packets to the server.
# File lib/em-ssh/connection.rb, line 265 def configure_client(options={}) @socket.client.set(options) end
Configure's the packet stream's server state with the given set of options. This is typically used to define the cipher, compression, and hmac algorithms to use when reading packets from the server.
# File lib/em-ssh/connection.rb, line 272 def configure_server(options={}) @socket.server.set(options) end
# File lib/em-ssh/connection.rb, line 142 def connection_completed [@contimeout, @nocon].each(&:cancel) @contimeout = nil @nocon = nil end
Sets a new hint for the packet stream, which the packet stream may use to change its behavior. (See PacketStream#hints
).
# File lib/em-ssh/connection.rb, line 278 def hint(which, value=true) @socket.hints[which] = value end
Returns the host (and possibly IP address) in a format compatible with SSH known-host files.
# File lib/em-ssh/connection.rb, line 241 def host_as_string @host_as_string ||= "#{host}".tap do |string| string = "[#{string}]:#{port}" if port != DEFAULT_PORT _, ip = Socket.unpack_sockaddr_in(get_peername) if ip != host string << "," << (port != DEFAULT_PORT ? "[#{ip}]:#{port}" : ip) end # ip != host end # |string| end
Taken from Net::SSH::Transport::Session
# File lib/em-ssh/connection.rb, line 252 def host_keys @host_keys ||= begin known_hosts = options.fetch(:known_hosts, Net::SSH::KnownHosts) known_hosts.search_for(options[:host_key_alias] || host_as_string, options) end end
# File lib/em-ssh/connection.rb, line 54 def next_message return @queue.shift if @queue.any? && algorithms.allow?(@queue.first) f = Fiber.current cb = on(:packet) do |packet| if @queue.any? && algorithms.allow?(@queue.first) cb.cancel f.resume(@queue.shift) end end # :packet return Fiber.yield end
Returns a hash of information about the peer (remote) side of the socket, including :ip, :port, :host, and :canonized (see host_as_string
).
# File lib/em-ssh/connection.rb, line 290 def peer @peer ||= {}.tap do |p| _, ip = Socket.unpack_sockaddr_in(get_peername) p[:ip] = ip p[:port] = @port.to_i p[:host] = @host p[:canonized] = host_as_string end end
EventMachine
callbacks
# File lib/em-ssh/connection.rb, line 98 def post_init @socket = PacketStream.new(self) @data = @socket.input end
# File lib/em-ssh/connection.rb, line 136 def receive_data(data) debug("read #{data.length} bytes") @data.append(data) fire(:data, data) end
Requests a rekey operation, and simulates a block until the operation completes. If a rekey is already pending, this returns immediately, having no effect.
# File lib/em-ssh/connection.rb, line 74 def rekey! if !algorithms.pending? f = Fiber.current on_next(:algo_init) do f.resume end # :algo_init algorithms.rekey! return Fiber.yield end end
Returns immediately if a rekey is already in process. Otherwise, if a rekey is needed (as indicated by the socket, see PacketStream#if_needs_rekey?
) one is performed, causing this method to block until it completes.
# File lib/em-ssh/connection.rb, line 88 def rekey_as_needed return if algorithms.pending? socket.if_needs_rekey? { rekey! } end
Send a packet to the server
# File lib/em-ssh/connection.rb, line 49 def send_message(message) @socket.send_packet(message) end
Returns a new service_request
packet for the given service name, ready for sending to the server.
# File lib/em-ssh/connection.rb, line 68 def service_request(service) Net::SSH::Buffer.from(:byte, SERVICE_REQUEST, :string, service) end
# File lib/em-ssh/connection.rb, line 103 def unbind debug("#{self} is unbound") fire(:closed) @closed = true @socket && @socket.close @socket = nil @data = nil @error_callback = nil failed_timeout = [@contimeout, @negotimeout, @algotimeout].find { |t| t } instance_variables.each do |iv| if (t = instance_variable_get(iv)) && (t.is_a?(EM::Timer) || t.is_a?(EM::PeriodicTimer)) t.cancel instance_variable_set(iv, nil) end end if @algorithms @algorithms.instance_variable_set(:@session, nil) @algorithms = nil end callbacks.values.flatten.each(&:cancel) callbacks.clear instance_variables.select{|iv| (t = instance_variable_get(iv)) && t.is_a?(EM::Ssh::Callbacks::Callback) }.each do |iv| instance_variable_set(iv, nil) end if failed_timeout fail(@disconnect ? EM::Ssh::Disconnect.from_packet(@disconnect) : NegotiationTimeout.new(@host)) end end
Private Instance Methods
Register the primary :data callback @return [Callback] the callback that was registered
# File lib/em-ssh/connection.rb, line 304 def register_data_handler on(:data) do |data| while (packet = @socket.poll_next_packet) case packet.type when DISCONNECT @disconnect = packet close_connection when IGNORE debug("IGNORE packet received: #{packet[:data].inspect}") when UNIMPLEMENTED log.warn("UNIMPLEMENTED: #{packet[:number]}") when DEBUG log.send((packet[:always_display] ? :fatal : :debug), packet[:message]) when KEXINIT Fiber.new do begin algorithms.accept_kexinit(packet) fire(:algo_init) if algorithms.initialized? rescue Exception => e fire(:error, e) end # begin end.resume else @queue.push(packet) unless packet.type >= CHANNEL_OPEN if algorithms.allow?(packet) fire(:packet, packet) fire(:session_packet, packet) if packet.type >= GLOBAL_REQUEST end # algorithms.allow?(packet) socket.consume! end # packet.type end # (packet = @socket.poll_next_packet) end # |data| end
Instantiates a new host-key verification class, based on the value of the parameter. When true or nil, the default Lenient verifier is returned. If it is false, the Null verifier is returned, and if it is :very, the Strict verifier is returned. If the argument happens to respond to :verify, it is returned directly. Otherwise, an exception is raised. Taken from Net::SSH::Session
# File lib/em-ssh/connection.rb, line 345 def select_host_key_verifier(paranoid) case paranoid when true, nil then Net::SSH::Verifiers::AcceptNewOrLocalTunnel.new when false then Net::SSH::Verifiers::Never.new when :very then Net::SSH::Verifiers::AcceptNew.new else if paranoid.respond_to?(:verify) paranoid else fail(@host, ArgumentError.new("argument to :paranoid is not valid: #{paranoid.inspect}")) close_connection end # paranoid.respond_to?(:verify) end # paranoid end