[project @ Arch-1:robey@lag.net--2005-master-shake%paramiko--dev--1--patch-59]

add auth_none and auth_interactive to support 'no auth' and 'keyboard-interactive auth'; for password auth, add a mode where it will try to fallback to keyboard-interactive in a pinch
This commit is contained in:
Robey Pointer 2005-09-21 22:42:11 +00:00
parent 24045332c5
commit 364479610e
3 changed files with 172 additions and 6 deletions

View File

@ -57,6 +57,16 @@ class AuthHandler (object):
else:
return self.username
def auth_none(self, username, event):
self.transport.lock.acquire()
try:
self.auth_event = event
self.auth_method = 'none'
self.username = username
self._request_auth()
finally:
self.transport.lock.release()
def auth_publickey(self, username, key, event):
self.transport.lock.acquire()
try:
@ -79,6 +89,21 @@ class AuthHandler (object):
finally:
self.transport.lock.release()
def auth_interactive(self, username, handler, event, submethods=''):
"""
response_list = handler(title, instructions, prompt_list)
"""
self.transport.lock.acquire()
try:
self.auth_event = event
self.auth_method = 'keyboard-interactive'
self.username = username
self.interactive_handler = handler
self.submethods = submethods
self._request_auth()
finally:
self.transport.lock.release()
def abort(self):
if self.auth_event is not None:
self.auth_event.set()
@ -175,6 +200,11 @@ class AuthHandler (object):
blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username)
sig = self.private_key.sign_ssh_data(self.transport.randpool, blob)
m.add_string(str(sig))
elif self.auth_method == 'keyboard-interactive':
m.add_string('')
m.add_string(self.submethods)
elif self.auth_method == 'none':
pass
else:
raise SSHException('Unknown auth method "%s"' % self.auth_method)
self.transport._send_message(m)
@ -302,6 +332,25 @@ class AuthHandler (object):
lang = m.get_string()
self.transport._log(INFO, 'Auth banner: ' + banner)
# who cares.
def _parse_userauth_info_request(self, m):
if self.auth_method != 'keyboard-interactive':
raise SSHException('Illegal info request from server')
title = m.get_string()
instructions = m.get_string()
m.get_string() # lang
prompts = m.get_int()
prompt_list = []
for i in range(prompts):
prompt_list.append((m.get_string(), m.get_boolean()))
response_list = self.interactive_handler(title, instructions, prompt_list)
m = Message()
m.add_byte(chr(MSG_USERAUTH_INFO_RESPONSE))
m.add_int(len(response_list))
for r in response_list:
m.add_string(r)
self.transport._send_message(m)
_handler_table = {
MSG_SERVICE_REQUEST: _parse_service_request,
@ -310,6 +359,7 @@ class AuthHandler (object):
MSG_USERAUTH_SUCCESS: _parse_userauth_success,
MSG_USERAUTH_FAILURE: _parse_userauth_failure,
MSG_USERAUTH_BANNER: _parse_userauth_banner,
MSG_USERAUTH_INFO_REQUEST: _parse_userauth_info_request,
}

View File

@ -26,6 +26,7 @@ MSG_KEXINIT, MSG_NEWKEYS = range(20, 22)
MSG_USERAUTH_REQUEST, MSG_USERAUTH_FAILURE, MSG_USERAUTH_SUCCESS, \
MSG_USERAUTH_BANNER = range(50, 54)
MSG_USERAUTH_PK_OK = 60
MSG_USERAUTH_INFO_REQUEST, MSG_USERAUTH_INFO_RESPONSE = range(60, 62)
MSG_GLOBAL_REQUEST, MSG_REQUEST_SUCCESS, MSG_REQUEST_FAILURE = range(80, 83)
MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, \
MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_DATA, MSG_CHANNEL_EXTENDED_DATA, \
@ -51,7 +52,8 @@ MSG_NAMES = {
MSG_USERAUTH_FAILURE: 'userauth-failure',
MSG_USERAUTH_SUCCESS: 'userauth-success',
MSG_USERAUTH_BANNER: 'userauth--banner',
MSG_USERAUTH_PK_OK: 'userauth-pk-ok',
MSG_USERAUTH_PK_OK: 'userauth-60(pk-ok/info-request)',
MSG_USERAUTH_INFO_RESPONSE: 'userauth-info-response',
MSG_GLOBAL_REQUEST: 'global-request',
MSG_REQUEST_SUCCESS: 'request-success',
MSG_REQUEST_FAILURE: 'request-failure',

View File

@ -24,7 +24,7 @@ import sys, os, string, threading, socket, struct, time
import weakref
from common import *
from ssh_exception import SSHException
from ssh_exception import SSHException, BadAuthenticationType
from message import Message
from channel import Channel
from sftp_client import SFTPClient
@ -869,7 +869,34 @@ class Transport (threading.Thread):
return None
return self.auth_handler.get_username()
def auth_password(self, username, password, event=None):
def auth_none(self, username):
"""
Try to authenticate to the server using no authentication at all.
This will almost always fail. It may be useful for determining the
list of authentication types supported by the server, by catching the
L{BadAuthenticationType} exception raised.
@param username: the username to authenticate as
@type username: string
@return: list of auth types permissible for the next stage of
authentication (normally empty)
@rtype: list
@raise BadAuthenticationType: if "none" authentication isn't allowed
by the server for this user
@raise SSHException: if the authentication failed due to a network
error
@since: 1.5
"""
if (not self.active) or (not self.initial_kex_done):
raise SSHException('No existing session')
my_event = threading.Event()
self.auth_handler = AuthHandler(self)
self.auth_handler.auth_none(username, my_event)
return self.auth_handler.wait_for_response(my_event)
def auth_password(self, username, password, event=None, fallback=True):
"""
Authenticate to the server using a password. The username and password
are sent over an encrypted link.
@ -883,6 +910,15 @@ class Transport (threading.Thread):
authentication succeeds or fails. On failure, an exception is raised.
Otherwise, the method simply returns.
Since 1.5, if no event is passed and C{fallback} is C{True} (the
default), if the server doesn't support plain password authentication
but does support so-called "keyboard-interactive" mode, an attempt
will be made to authenticate using this interactive mode. If it fails,
the normal exception will be thrown as if the attempt had never been
made. This is useful for some recent Gentoo and Debian distributions,
which turn off plain password authentication in a misguided belief
that interactive authentication is "more secure". (It's not.)
If the server requires multi-step authentication (which is very rare),
this method will return a list of auth types permissible for the next
step. Otherwise, in the normal case, an empty list is returned.
@ -894,6 +930,10 @@ class Transport (threading.Thread):
@param event: an event to trigger when the authentication attempt is
complete (whether it was successful or not)
@type event: threading.Event
@param fallback: C{True} if an attempt at an automated "interactive"
password auth should be made if the server doesn't support normal
password auth
@type fallback: bool
@return: list of auth types permissible for the next stage of
authentication (normally empty)
@rtype: list
@ -915,7 +955,28 @@ class Transport (threading.Thread):
if event is not None:
# caller wants to wait for event themselves
return []
return self.auth_handler.wait_for_response(my_event)
try:
return self.auth_handler.wait_for_response(my_event)
except BadAuthenticationType, x:
# if password auth isn't allowed, but keyboard-interactive *is*, try to fudge it
if not fallback or not 'keyboard-interactive' in x.allowed_types:
raise
try:
def handler(title, instructions, fields):
self._log(DEBUG, 'title=%r, instructions=%r, fields=%r' % (title, instructions, fields))
if len(fields) > 1:
raise SSHException('Fallback authentication failed.')
if len(fields) == 0:
# for some reason, at least on os x, a 2nd request will
# be made with zero fields requested. maybe it's just
# to try to fake out automated scripting of the exact
# type we're doing here. *shrug* :)
return []
return [ password ]
return self.auth_interactive(username, handler)
except SSHException, ignored:
# attempt failed; just raise the original exception
raise x
def auth_publickey(self, username, key, event=None):
"""
@ -935,9 +996,9 @@ class Transport (threading.Thread):
this method will return a list of auth types permissible for the next
step. Otherwise, in the normal case, an empty list is returned.
@param username: the username to authenticate as.
@param username: the username to authenticate as
@type username: string
@param key: the private key to authenticate with.
@param key: the private key to authenticate with
@type key: L{PKey <pkey.PKey>}
@param event: an event to trigger when the authentication attempt is
complete (whether it was successful or not)
@ -964,6 +1025,59 @@ class Transport (threading.Thread):
# caller wants to wait for event themselves
return []
return self.auth_handler.wait_for_response(my_event)
def auth_interactive(self, username, handler, submethods=''):
"""
Authenticate to the server interactively. A handler is used to answer
arbitrary questions from the server. On many servers, this is just a
dumb wrapper around PAM.
This method will block until the authentication succeeds or fails,
peroidically calling the handler asynchronously to get answers to
authentication questions. The handler may be called more than once
if the server continues to ask questions.
The handler is expected to be a callable that will handle calls of the
form: C{handler(title, instructions, prompt_list)}. The C{title} is
meant to be a dialog-window title, and the C{instructions} are user
instructions (both are strings). C{prompt_list} will be a list of
prompts, each prompt being a tuple of C{(str, bool)}. The string is
the prompt and the boolean indicates whether the user text should be
echoed.
A sample call would thus be:
C{handler('title', 'instructions', [('Password:', False)])}.
The handler should return a list or tuple of answers to the server's
questions.
If the server requires multi-step authentication (which is very rare),
this method will return a list of auth types permissible for the next
step. Otherwise, in the normal case, an empty list is returned.
@param username: the username to authenticate as
@type username: string
@param handler: a handler for responding to server questions
@type handler: callable
@param submethods: a string list of desired submethods (optional)
@type submethods: str
@return: list of auth types permissible for the next stage of
authentication (normally empty).
@rtype: list
@raise BadAuthenticationType: if public-key authentication isn't
allowed by the server for this user
@raise SSHException: if the authentication failed
@since: 1.5
"""
if (not self.active) or (not self.initial_kex_done):
# we should never try to authenticate unless we're on a secure link
raise SSHException('No existing session')
my_event = threading.Event()
self.auth_handler = AuthHandler(self)
self.auth_handler.auth_interactive(username, handler, my_event, submethods)
return self.auth_handler.wait_for_response(my_event)
def set_log_channel(self, name):
"""