Class: EventMachine::Ssh::Connection

Inherits:
Connection
  • Object
show all
Includes:
EM::Deferrable, Callbacks, Log
Defined in:
lib/em-ssh/connection.rb,
lib/em-ssh/connection/channel/interactive.rb,
lib/em-ssh/connection/channel/null-logger.rb

Overview

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

Defined Under Namespace

Classes: Channel

Constant Summary collapse

TIMEOUT =

maximum number of seconds to wait for a connection

20

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Callbacks

#callbacks, #callbacks=, #fire, #on, #on_next

Methods included from Log

#debug, #error, #fatal, #info, #log, #warn

Constructor Details

#initialize(options = {}) ⇒ Connection

Returns a new instance of Connection.



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# 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

Instance Attribute Details

#algorithmsObject (readonly)

The Algorithms instance used to perform key exchanges.



26
27
28
# File 'lib/em-ssh/connection.rb', line 26

def algorithms
  @algorithms
end

#hostString (readonly)

Returns The host to connect to, as given to the constructor.

Returns:

  • (String)

    The host to connect to, as given to the constructor.



17
18
19
# File 'lib/em-ssh/connection.rb', line 17

def host
  @host
end

#host_key_verifierObject (readonly)

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



29
30
31
# File 'lib/em-ssh/connection.rb', line 29

def host_key_verifier
  @host_key_verifier
end

#optionsObject (readonly)

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



32
33
34
# File 'lib/em-ssh/connection.rb', line 32

def options
  @options
end

#portFixnum (readonly)

Returns the port number (DEFAULT_PORT) to connect to, as given in the options to the constructor.

Returns:

  • (Fixnum)

    the port number (DEFAULT_PORT) to connect to, as given in the options to the constructor.



20
21
22
# File 'lib/em-ssh/connection.rb', line 20

def port
  @port
end

#server_versionServerVersion (readonly)

Returns The ServerVersion instance that encapsulates the negotiated protocol version.

Returns:

  • (ServerVersion)

    The ServerVersion instance that encapsulates the negotiated protocol version.



23
24
25
# File 'lib/em-ssh/connection.rb', line 23

def server_version
  @server_version
end

#socketPacketStream (readonly)

Returns emulates a socket and ssh packetstream.

Returns:



35
36
37
# File 'lib/em-ssh/connection.rb', line 35

def socket
  @socket
end

Instance Method Details

#closeObject

Close the connection



43
44
45
46
# File 'lib/em-ssh/connection.rb', line 43

def close
  # #unbind will update @closed
  close_connection
end

#closed?Boolean

Returns true if the connection has been closed.

Returns:

  • (Boolean)

    true if the connection has been closed



38
39
40
# File 'lib/em-ssh/connection.rb', line 38

def closed?
  @closed == true
end

#configure_client(options = {}) ⇒ Object

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.



265
266
267
# File 'lib/em-ssh/connection.rb', line 265

def configure_client(options={})
  @socket.client.set(options)
end

#configure_server(options = {}) ⇒ Object

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.



272
273
274
# File 'lib/em-ssh/connection.rb', line 272

def configure_server(options={})
  @socket.server.set(options)
end

#connection_completedObject



142
143
144
145
146
# File 'lib/em-ssh/connection.rb', line 142

def connection_completed
  [@contimeout, @nocon].each(&:cancel)
  @contimeout = nil
  @nocon = nil
end

#hint(which, value = true) ⇒ Object

Sets a new hint for the packet stream, which the packet stream may use to change its behavior. (See PacketStream#hints).



278
279
280
# File 'lib/em-ssh/connection.rb', line 278

def hint(which, value=true)
  @socket.hints[which] = value
end

#host_as_stringObject

Returns the host (and possibly IP address) in a format compatible with SSH known-host files.



241
242
243
244
245
246
247
248
249
# 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_keysObject

Taken from Net::SSH::Transport::Session



252
253
254
255
256
257
# 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_messageObject



54
55
56
57
58
59
60
61
62
63
64
# 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

#peerObject

Returns a hash of information about the peer (remote) side of the socket, including :ip, :port, :host, and :canonized (see #host_as_string).



290
291
292
293
294
295
296
297
298
# 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_initObject

EventMachine callbacks



98
99
100
101
# File 'lib/em-ssh/connection.rb', line 98

def post_init
  @socket = PacketStream.new(self)
  @data         = @socket.input
end

#receive_data(data) ⇒ Object



136
137
138
139
140
# 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!Object

Requests a rekey operation, and simulates a block until the operation completes. If a rekey is already pending, this returns immediately, having no effect.



74
75
76
77
78
79
80
81
82
83
# 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_neededObject

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.



88
89
90
91
# 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) ⇒ Object Also known as: enqueue_message

Send a packet to the server



49
50
51
# File 'lib/em-ssh/connection.rb', line 49

def send_message(message)
  @socket.send_packet(message)
end

#service_request(service) ⇒ Object

Returns a new service_request packet for the given service name, ready for sending to the server.



68
69
70
# File 'lib/em-ssh/connection.rb', line 68

def service_request(service)
  Net::SSH::Buffer.from(:byte, SERVICE_REQUEST, :string, service)
end

#unbindObject

post_init



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# 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