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

algorithms[R]

The Algorithms instance used to perform key exchanges.

host[R]

@return [String] The host to connect to, as given to the constructor.

host_key_verifier[R]

The host-key verifier object used to verify host keys, to ensure that the connection is not being spoofed.

options[R]

The hash of options that were given to the object at initialization.

port[R]

@return [Fixnum] the port number (DEFAULT_PORT) to connect to, as given in the options to the constructor.

server_version[R]

@return [ServerVersion] The ServerVersion instance that encapsulates the negotiated protocol version.

socket[R]

@return [PacketStream] emulates a socket and ssh packetstream

Public Class Methods

new(options = {}) click to toggle source
# 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() click to toggle source

Close the connection

# File lib/em-ssh/connection.rb, line 43
def close
  # #unbind will update @closed
  close_connection
end
closed?() click to toggle source

@return [Boolean] true if the connection has been closed

# File lib/em-ssh/connection.rb, line 38
def closed?
  @closed == true
end
configure_client(options={}) click to toggle source

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_server(options={}) click to toggle source

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
connection_completed() click to toggle source
# File lib/em-ssh/connection.rb, line 142
def connection_completed
  [@contimeout, @nocon].each(&:cancel)
  @contimeout = nil
  @nocon = nil
end
enqueue_message(message)
Alias for: send_message
hint(which, value=true) click to toggle source

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
host_as_string() click to toggle source

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
host_keys() click to toggle source

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
next_message() click to toggle source
# 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
peer() click to toggle source

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
post_init() click to toggle source

EventMachine callbacks

# File lib/em-ssh/connection.rb, line 98
def post_init
  @socket = PacketStream.new(self)
  @data         = @socket.input
end
receive_data(data) click to toggle source
# File lib/em-ssh/connection.rb, line 136
def receive_data(data)
  debug("read #{data.length} bytes")
  @data.append(data)
  fire(:data, data)
end
rekey!() click to toggle source

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
rekey_as_needed() click to toggle source

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_message(message) click to toggle source

Send a packet to the server

# File lib/em-ssh/connection.rb, line 49
def send_message(message)
  @socket.send_packet(message)
end
Also aliased as: enqueue_message
service_request(service) click to toggle source

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
unbind() click to toggle source
# 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_data_handler() click to toggle source

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
select_host_key_verifier(paranoid) click to toggle source

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