134 lines
4.5 KiB
Ruby
134 lines
4.5 KiB
Ruby
|
require 'net/ssh/loggable'
|
||
|
require 'net/ssh/transport/constants'
|
||
|
require 'net/ssh/authentication/constants'
|
||
|
require 'net/ssh/authentication/key_manager'
|
||
|
require 'net/ssh/authentication/methods/publickey'
|
||
|
require 'net/ssh/authentication/methods/hostbased'
|
||
|
require 'net/ssh/authentication/methods/password'
|
||
|
require 'net/ssh/authentication/methods/keyboard_interactive'
|
||
|
|
||
|
module Net; module SSH; module Authentication
|
||
|
|
||
|
# Represents an authentication session. It manages the authentication of
|
||
|
# a user over an established connection (the "transport" object, see
|
||
|
# Net::SSH::Transport::Session).
|
||
|
#
|
||
|
# The use of an authentication session to manage user authentication is
|
||
|
# internal to Net::SSH (specifically Net::SSH.start). Consumers of the
|
||
|
# Net::SSH library will never need to access this class directly.
|
||
|
class Session
|
||
|
include Transport::Constants, Constants, Loggable
|
||
|
|
||
|
# transport layer abstraction
|
||
|
attr_reader :transport
|
||
|
|
||
|
# the list of authentication methods to try
|
||
|
attr_reader :auth_methods
|
||
|
|
||
|
# the list of authentication methods that are allowed
|
||
|
attr_reader :allowed_auth_methods
|
||
|
|
||
|
# a hash of options, given at construction time
|
||
|
attr_reader :options
|
||
|
|
||
|
# Instantiates a new Authentication::Session object over the given
|
||
|
# transport layer abstraction.
|
||
|
def initialize(transport, options={})
|
||
|
self.logger = transport.logger
|
||
|
@transport = transport
|
||
|
|
||
|
@auth_methods = options[:auth_methods] || %w(publickey hostbased password keyboard-interactive)
|
||
|
@options = options
|
||
|
|
||
|
@allowed_auth_methods = @auth_methods
|
||
|
end
|
||
|
|
||
|
# Attempts to authenticate the given user, in preparation for the next
|
||
|
# service request. Returns true if an authentication method succeeds in
|
||
|
# authenticating the user, and false otherwise.
|
||
|
def authenticate(next_service, username, password=nil)
|
||
|
debug { "beginning authentication of `#{username}'" }
|
||
|
|
||
|
transport.send_message(transport.service_request("ssh-userauth"))
|
||
|
message = expect_message(SERVICE_ACCEPT)
|
||
|
|
||
|
key_manager = KeyManager.new(logger, options)
|
||
|
keys.each { |key| key_manager.add(key) } unless keys.empty?
|
||
|
key_data.each { |key2| key_manager.add_key_data(key2) } unless key_data.empty?
|
||
|
|
||
|
attempted = []
|
||
|
|
||
|
@auth_methods.each do |name|
|
||
|
next unless @allowed_auth_methods.include?(name)
|
||
|
attempted << name
|
||
|
|
||
|
debug { "trying #{name}" }
|
||
|
method = Methods.const_get(name.split(/\W+/).map { |p| p.capitalize }.join).new(self, :key_manager => key_manager)
|
||
|
|
||
|
return true if method.authenticate(next_service, username, password)
|
||
|
end
|
||
|
|
||
|
error { "all authorization methods failed (tried #{attempted.join(', ')})" }
|
||
|
return false
|
||
|
ensure
|
||
|
key_manager.finish if key_manager
|
||
|
end
|
||
|
|
||
|
# Blocks until a packet is received. It silently handles USERAUTH_BANNER
|
||
|
# packets, and will raise an error if any packet is received that is not
|
||
|
# valid during user authentication.
|
||
|
def next_message
|
||
|
loop do
|
||
|
packet = transport.next_message
|
||
|
|
||
|
case packet.type
|
||
|
when USERAUTH_BANNER
|
||
|
info { packet[:message] }
|
||
|
# TODO add a hook for people to retrieve the banner when it is sent
|
||
|
|
||
|
when USERAUTH_FAILURE
|
||
|
@allowed_auth_methods = packet[:authentications].split(/,/)
|
||
|
debug { "allowed methods: #{packet[:authentications]}" }
|
||
|
return packet
|
||
|
|
||
|
when USERAUTH_METHOD_RANGE, SERVICE_ACCEPT
|
||
|
return packet
|
||
|
|
||
|
when USERAUTH_SUCCESS
|
||
|
transport.hint :authenticated
|
||
|
return packet
|
||
|
|
||
|
else
|
||
|
raise Net::SSH::Exception, "unexpected message #{packet.type} (#{packet})"
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Blocks until a packet is received, and returns it if it is of the given
|
||
|
# type. If it is not, an exception is raised.
|
||
|
def expect_message(type)
|
||
|
message = next_message
|
||
|
unless message.type == type
|
||
|
raise Net::SSH::Exception, "expected #{type}, got #{message.type} (#{message})"
|
||
|
end
|
||
|
message
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
# Returns an array of paths to the key files that should be used when
|
||
|
# attempting any key-based authentication mechanism.
|
||
|
def keys
|
||
|
Array(
|
||
|
options[:keys] ||
|
||
|
%w(~/.ssh/id_dsa ~/.ssh/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_rsa)
|
||
|
)
|
||
|
end
|
||
|
|
||
|
# Returns an array of the key data that should be used when
|
||
|
# attempting any key-based authentication mechanism.
|
||
|
def key_data
|
||
|
Array(options[:key_data])
|
||
|
end
|
||
|
end
|
||
|
end; end; end
|