begin require 'rubygems' gem 'net-ssh', ">= 1.99.1" rescue LoadError, NameError end require 'net/ssh' module Capistrano # A helper class for dealing with SSH connections. class SSH # Patch an accessor onto an SSH connection so that we can record the server # definition object that defines the connection. This is useful because # the gateway returns connections whose "host" is 127.0.0.1, instead of # the host on the other side of the tunnel. module Server #:nodoc: def self.apply_to(connection, server) connection.extend(Server) connection.xserver = server connection end attr_accessor :xserver end # An abstraction to make it possible to connect to the server via public key # without prompting for the password. If the public key authentication fails # this will fall back to password authentication. # # +server+ must be an instance of ServerDefinition. # # If a block is given, the new session is yielded to it, otherwise the new # session is returned. # # If an :ssh_options key exists in +options+, it is passed to the Net::SSH # constructor. Values in +options+ are then merged into it, and any # connection information in +server+ is added last, so that +server+ info # takes precedence over +options+, which takes precendence over ssh_options. def self.connect(server, options={}) connection_strategy(server, options) do |host, user, connection_options| connection = Net::SSH.start(host, user, connection_options) Server.apply_to(connection, server) end end # Abstracts the logic for establishing an SSH connection (which includes # testing for connection failures and retrying with a password, and so forth, # mostly made complicated because of the fact that some of these variables # might be lazily evaluated and try to do something like prompt the user, # which should only happen when absolutely necessary. # # This will yield the hostname, username, and a hash of connection options # to the given block, which should return a new connection. def self.connection_strategy(server, options={}, &block) methods = [ %w(publickey hostbased), %w(password keyboard-interactive) ] password_value = nil ssh_options = (server.options[:ssh_options] || {}).merge(options[:ssh_options] || {}) user = server.user || options[:user] || ssh_options[:username] || ServerDefinition.default_user port = server.port || options[:port] || ssh_options[:port] ssh_options[:port] = port if port ssh_options.delete(:username) begin connection_options = ssh_options.merge( :password => password_value, :auth_methods => ssh_options[:auth_methods] || methods.shift ) yield server.host, user, connection_options rescue Net::SSH::AuthenticationFailed raise if methods.empty? || ssh_options[:auth_methods] password_value = options[:password] retry end end end end